diff --git a/VIRTUOS_ExpansionPluginTests/.gitattributes b/VIRTUOS_ExpansionPluginTests/.gitattributes new file mode 100644 index 0000000..3373152 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.bat eol=crlf \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/.gitignore b/VIRTUOS_ExpansionPluginTests/.gitignore new file mode 100644 index 0000000..c66e5a0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/.gitignore @@ -0,0 +1,13 @@ + +.hg/ +binaries/ +deriveddatacache/ +.vs/ +build/ +intermediate/ +PACKPLUGIN/ +COMPPLUGIN/ +saved/ +*.orig +*.sln +*.xlsx diff --git a/VIRTUOS_ExpansionPluginTests/.vsconfig b/VIRTUOS_ExpansionPluginTests/.vsconfig new file mode 100644 index 0000000..1a9d718 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/.vsconfig @@ -0,0 +1,13 @@ +{ + "version": "1.0", + "components": [ + "Microsoft.Net.Component.4.6.2.TargetingPack", + "Microsoft.VisualStudio.Component.VC.14.36.17.6.x86.x64", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "Microsoft.VisualStudio.Component.Windows10SDK.22000", + "Microsoft.VisualStudio.Workload.CoreEditor", + "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Workload.NativeDesktop", + "Microsoft.VisualStudio.Workload.NativeGame" + ] +} diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultEditor.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultEditor.ini new file mode 100644 index 0000000..f0a38bf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultEditor.ini @@ -0,0 +1,8 @@ +[/Script/UnrealEd.AssetViewerSettings] +-Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=1.000000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=0.675000,BloomThreshold=-1.000000,BloomSizeScale=4.000000,Bloom1Size=0.300000,Bloom2Size=1.000000,Bloom3Size=2.000000,Bloom4Size=10.000000,Bloom5Size=30.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.346500,G=0.346500,B=0.346500,A=1.000000),Bloom2Tint=(R=0.138000,G=0.138000,B=0.138000,A=1.000000),Bloom3Tint=(R=0.117600,G=0.117600,B=0.117600,A=1.000000),Bloom4Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom5Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom6Tint=(R=0.061000,G=0.061000,B=0.061000,A=1.000000),BloomDirtMaskIntensity=0.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) +-Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=1.000000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=0.675000,BloomThreshold=-1.000000,BloomSizeScale=4.000000,Bloom1Size=0.300000,Bloom2Size=1.000000,Bloom3Size=2.000000,Bloom4Size=10.000000,Bloom5Size=30.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.346500,G=0.346500,B=0.346500,A=1.000000),Bloom2Tint=(R=0.138000,G=0.138000,B=0.138000,A=1.000000),Bloom3Tint=(R=0.117600,G=0.117600,B=0.117600,A=1.000000),Bloom4Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom5Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom6Tint=(R=0.061000,G=0.061000,B=0.061000,A=1.000000),BloomDirtMaskIntensity=0.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) +-Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=0.180000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=1.000000,BloomThreshold=1.000000,BloomSizeScale=4.000000,Bloom1Size=1.000000,Bloom2Size=4.000000,Bloom3Size=16.000000,Bloom4Size=32.000000,Bloom5Size=64.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom2Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom3Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom4Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom5Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom6Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMaskIntensity=1.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) ++Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=1.000000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=0.675000,BloomThreshold=-1.000000,BloomSizeScale=4.000000,Bloom1Size=0.300000,Bloom2Size=1.000000,Bloom3Size=2.000000,Bloom4Size=10.000000,Bloom5Size=30.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.346500,G=0.346500,B=0.346500,A=1.000000),Bloom2Tint=(R=0.138000,G=0.138000,B=0.138000,A=1.000000),Bloom3Tint=(R=0.117600,G=0.117600,B=0.117600,A=1.000000),Bloom4Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom5Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom6Tint=(R=0.061000,G=0.061000,B=0.061000,A=1.000000),BloomDirtMaskIntensity=0.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) ++Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=1.000000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=0.675000,BloomThreshold=-1.000000,BloomSizeScale=4.000000,Bloom1Size=0.300000,Bloom2Size=1.000000,Bloom3Size=2.000000,Bloom4Size=10.000000,Bloom5Size=30.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.346500,G=0.346500,B=0.346500,A=1.000000),Bloom2Tint=(R=0.138000,G=0.138000,B=0.138000,A=1.000000),Bloom3Tint=(R=0.117600,G=0.117600,B=0.117600,A=1.000000),Bloom4Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom5Tint=(R=0.066000,G=0.066000,B=0.066000,A=1.000000),Bloom6Tint=(R=0.061000,G=0.061000,B=0.061000,A=1.000000),BloomDirtMaskIntensity=0.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) ++Profiles=(ProfileName="Default",DirectionalLightIntensity=2.620000,DirectionalLightColor=(R=0.990000,G=0.839850,B=0.732600,A=1.000000),SkyLightIntensity=0.880000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,EnvironmentCubeMapPath="",PostProcessingSettings=(bOverride_WhiteTemp=True,bOverride_WhiteTint=False,bOverride_ColorSaturation=True,bOverride_ColorContrast=True,bOverride_ColorGamma=True,bOverride_ColorGain=True,bOverride_ColorOffset=True,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_FilmWhitePoint=False,bOverride_FilmSaturation=False,bOverride_FilmChannelMixerRed=False,bOverride_FilmChannelMixerGreen=False,bOverride_FilmChannelMixerBlue=False,bOverride_FilmContrast=False,bOverride_FilmDynamicRange=False,bOverride_FilmHealAmount=False,bOverride_FilmToeAmount=False,bOverride_FilmShadowTint=False,bOverride_FilmShadowTintBlend=False,bOverride_FilmShadowTintAmount=False,bOverride_FilmSlope=True,bOverride_FilmToe=True,bOverride_FilmShoulder=True,bOverride_FilmBlackClip=True,bOverride_FilmWhiteClip=True,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomIntensity=True,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_AutoExposureMethod=True,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=True,bOverride_HistogramLogMin=True,bOverride_HistogramLogMax=True,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=True,bOverride_GrainIntensity=False,bOverride_GrainJitter=False,bOverride_AmbientOcclusionIntensity=True,bOverride_AmbientOcclusionStaticFraction=True,bOverride_AmbientOcclusionRadius=True,bOverride_AmbientOcclusionFadeDistance=False,bOverride_AmbientOcclusionFadeRadius=False,bOverride_AmbientOcclusionDistance=False,bOverride_AmbientOcclusionRadiusInWS=False,bOverride_AmbientOcclusionPower=True,bOverride_AmbientOcclusionBias=True,bOverride_AmbientOcclusionQuality=True,bOverride_AmbientOcclusionMipBlend=True,bOverride_AmbientOcclusionMipScale=True,bOverride_AmbientOcclusionMipThreshold=True,bOverride_LPVIntensity=False,bOverride_LPVDirectionalOcclusionIntensity=False,bOverride_LPVDirectionalOcclusionRadius=False,bOverride_LPVDiffuseOcclusionExponent=False,bOverride_LPVSpecularOcclusionExponent=False,bOverride_LPVDiffuseOcclusionIntensity=False,bOverride_LPVSpecularOcclusionIntensity=False,bOverride_LPVSize=False,bOverride_LPVSecondaryOcclusionIntensity=False,bOverride_LPVSecondaryBounceIntensity=False,bOverride_LPVGeometryVolumeBias=False,bOverride_LPVVplInjectionBias=False,bOverride_LPVEmissiveInjectionIntensity=False,bOverride_IndirectLightingColor=False,bOverride_IndirectLightingIntensity=False,bOverride_ColorGradingIntensity=True,bOverride_ColorGradingLUT=True,bOverride_DepthOfFieldFocalDistance=False,bOverride_DepthOfFieldFstop=False,bOverride_DepthOfFieldSensorWidth=False,bOverride_DepthOfFieldDepthBlurRadius=False,bOverride_DepthOfFieldDepthBlurAmount=False,bOverride_DepthOfFieldFocalRegion=False,bOverride_DepthOfFieldNearTransitionRegion=False,bOverride_DepthOfFieldFarTransitionRegion=False,bOverride_DepthOfFieldScale=True,bOverride_DepthOfFieldMaxBokehSize=False,bOverride_DepthOfFieldNearBlurSize=False,bOverride_DepthOfFieldFarBlurSize=False,bOverride_DepthOfFieldMethod=True,bOverride_MobileHQGaussian=False,bOverride_DepthOfFieldBokehShape=False,bOverride_DepthOfFieldOcclusion=False,bOverride_DepthOfFieldColorThreshold=False,bOverride_DepthOfFieldSizeThreshold=False,bOverride_DepthOfFieldSkyFocusDistance=False,bOverride_DepthOfFieldVignetteSize=False,bOverride_MotionBlurAmount=False,bOverride_MotionBlurMax=False,bOverride_MotionBlurPerObjectSize=False,bOverride_ScreenPercentage=False,bOverride_ScreenSpaceReflectionIntensity=True,bOverride_ScreenSpaceReflectionQuality=True,bOverride_ScreenSpaceReflectionMaxRoughness=True,bOverride_ScreenSpaceReflectionRoughnessScale=False,WhiteTemp=6700.000000,WhiteTint=0.000000,ColorSaturation=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrast=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGamma=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGain=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffset=(X=0.005000,Y=0.005000,Z=0.005000,W=0.000000),ColorSaturationShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainShadows=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetShadows=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionShadowsMax=0.090000,ColorSaturationMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainMidtones=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetMidtones=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorSaturationHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorContrastHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGammaHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorGainHighlights=(X=1.000000,Y=1.000000,Z=1.000000,W=1.000000),ColorOffsetHighlights=(X=0.000000,Y=0.000000,Z=0.000000,W=0.000000),ColorCorrectionHighlightsMin=0.500000,FilmWhitePoint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),FilmShadowTintBlend=0.500000,FilmShadowTintAmount=0.000000,FilmSaturation=1.000000,FilmChannelMixerRed=(R=1.000000,G=0.000000,B=0.000000,A=1.000000),FilmChannelMixerGreen=(R=0.000000,G=1.000000,B=0.000000,A=1.000000),FilmChannelMixerBlue=(R=0.000000,G=0.000000,B=1.000000,A=1.000000),FilmContrast=0.030000,FilmToeAmount=1.000000,FilmHealAmount=0.180000,FilmDynamicRange=4.000000,FilmSlope=0.880000,FilmToe=0.550000,FilmShoulder=0.260000,FilmBlackClip=0.000000,FilmWhiteClip=0.040000,SceneColorTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SceneFringeIntensity=0.000000,BloomIntensity=1.000000,BloomThreshold=1.000000,BloomSizeScale=4.000000,Bloom1Size=1.000000,Bloom2Size=4.000000,Bloom3Size=16.000000,Bloom4Size=32.000000,Bloom5Size=64.000000,Bloom6Size=64.000000,Bloom1Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom2Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom3Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom4Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom5Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),Bloom6Tint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMaskIntensity=1.000000,BloomDirtMaskTint=(R=0.500000,G=0.500000,B=0.500000,A=1.000000),BloomDirtMask=None,AmbientCubemapTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),AmbientCubemapIntensity=1.000000,AmbientCubemap=None,AutoExposureMethod=AEM_Histogram,AutoExposureLowPercent=80.000000,AutoExposureHighPercent=98.300003,AutoExposureMinBrightness=1.000000,AutoExposureMaxBrightness=1.000000,AutoExposureSpeedUp=3.000000,AutoExposureSpeedDown=1.000000,AutoExposureBias=0.330000,HistogramLogMin=-8.000000,HistogramLogMax=4.000000,LensFlareIntensity=1.000000,LensFlareTint=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),LensFlareBokehSize=3.000000,LensFlareThreshold=8.000000,LensFlareBokehShape=None,LensFlareTints[0]=(R=1.000000,G=0.800000,B=0.400000,A=0.600000),LensFlareTints[1]=(R=1.000000,G=1.000000,B=0.600000,A=0.530000),LensFlareTints[2]=(R=0.800000,G=0.800000,B=1.000000,A=0.460000),LensFlareTints[3]=(R=0.500000,G=1.000000,B=0.400000,A=0.390000),LensFlareTints[4]=(R=0.500000,G=0.800000,B=1.000000,A=0.310000),LensFlareTints[5]=(R=0.900000,G=1.000000,B=0.800000,A=0.270000),LensFlareTints[6]=(R=1.000000,G=0.800000,B=0.400000,A=0.220000),LensFlareTints[7]=(R=0.900000,G=0.700000,B=0.700000,A=0.150000),VignetteIntensity=0.161468,GrainJitter=0.000000,GrainIntensity=0.000000,AmbientOcclusionIntensity=1.000000,AmbientOcclusionStaticFraction=1.000000,AmbientOcclusionRadius=73.477997,AmbientOcclusionRadiusInWS=False,AmbientOcclusionFadeDistance=8000.000000,AmbientOcclusionFadeRadius=5000.000000,AmbientOcclusionDistance=80.000000,AmbientOcclusionPower=1.200000,AmbientOcclusionBias=3.000000,AmbientOcclusionQuality=100.000000,AmbientOcclusionMipBlend=0.600000,AmbientOcclusionMipScale=1.700000,AmbientOcclusionMipThreshold=0.010000,IndirectLightingColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),IndirectLightingIntensity=1.000000,ColorGradingIntensity=0.000000,ColorGradingLUT=Texture2D'/Engine/EditorResources/RGBTable16x1_AssetViewer.RGBTable16x1_AssetViewer',DepthOfFieldMethod=DOFM_BokehDOF,bMobileHQGaussian=False,DepthOfFieldFstop=4.000000,DepthOfFieldSensorWidth=24.576000,DepthOfFieldFocalDistance=1000.000000,DepthOfFieldDepthBlurAmount=1.000000,DepthOfFieldDepthBlurRadius=0.000000,DepthOfFieldFocalRegion=0.000000,DepthOfFieldNearTransitionRegion=300.000000,DepthOfFieldFarTransitionRegion=500.000000,DepthOfFieldScale=0.000000,DepthOfFieldMaxBokehSize=15.000000,DepthOfFieldNearBlurSize=15.000000,DepthOfFieldFarBlurSize=15.000000,DepthOfFieldBokehShape=None,DepthOfFieldOcclusion=0.400000,DepthOfFieldColorThreshold=1.000000,DepthOfFieldSizeThreshold=0.080000,DepthOfFieldSkyFocusDistance=0.000000,DepthOfFieldVignetteSize=200.000000,MotionBlurAmount=0.500000,MotionBlurMax=5.000000,MotionBlurPerObjectSize=0.500000,LPVIntensity=1.000000,LPVVplInjectionBias=0.640000,LPVSize=5312.000000,LPVSecondaryOcclusionIntensity=0.000000,LPVSecondaryBounceIntensity=0.000000,LPVGeometryVolumeBias=0.384000,LPVEmissiveInjectionIntensity=1.000000,LPVDirectionalOcclusionIntensity=0.000000,LPVDirectionalOcclusionRadius=8.000000,LPVDiffuseOcclusionExponent=1.000000,LPVSpecularOcclusionExponent=7.000000,LPVDiffuseOcclusionIntensity=1.000000,LPVSpecularOcclusionIntensity=1.000000,ScreenSpaceReflectionIntensity=100.000000,ScreenSpaceReflectionQuality=100.000000,ScreenSpaceReflectionMaxRoughness=1.000000,ScreenPercentage=100.000000,WeightedBlendables=(Array=),Blendables=),bPostProcessingEnabled=True,LightingRigRotation=109.389069,RotationSpeed=2.000000,DirectionalLightRotation=(Pitch=-39.999985,Yaw=-67.500015,Roll=0.000000)) + diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultEngine.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultEngine.ini new file mode 100644 index 0000000..ce0235f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultEngine.ini @@ -0,0 +1,413 @@ +[HMDPluginPriority] +; Since SteamVR also works with the Oculus Rift and Windows Mixed Reality, give priority to the native Oculus and Windows Mixed Reality plugins before trying SteamVR +OpenXRHMD=60 +WindowsMixedRealityHMD=40 +OculusHMD=70 +SteamVR=10 + +[/Script/Engine.GameEngine] ++NetDriverDefinitions=(DefName="GameNetDriver2",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") ++NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") + + +[/Script/OnlineSubsystemSteam.SteamNetDriver] +NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection" + +[Voice] +bEnabled=true + +[OnlineSubsystem] +bHasVoiceEnabled=true +DefaultPlatformService=Steam + +[OnlineSubsystemSteam] +bEnabled=True +SteamDevAppId=480 + +[/Script/OnlineSubsystemUtils.IpNetDriver] +MaxClientRate=15000 +MaxInternetClientRate=15000 + +[/Script/Engine.Player] +ConfiguredInternetSpeed=15000 +ConfiguredLanSpeed=20000 + +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Game/VRE/ExampleMap/MotionControllerMap.MotionControllerMap +LocalMapOptions= +TransitionMap= +bUseSplitscreen=False +TwoPlayerSplitscreenLayout=Horizontal +ThreePlayerSplitscreenLayout=FavorTop +GameInstanceClass=/Game/VRE/Core/VRGameInstance.VRGameInstance_C +GameDefaultMap=/Game/VRE/ExampleMap/MotionControllerMap.MotionControllerMap +ServerDefaultMap=/Engine/Maps/Entry +GlobalDefaultGameMode=/Game/VRE/Core/SteamVR_GM.SteamVR_GM_C +GlobalDefaultServerGameMode=None + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_VirtualRealityBP",NewGameName="/Script/VRExpPluginExample") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_VirtualRealityBP",NewGameName="/Script/VRExpPluginExample") ++ActiveGameNameRedirects=(OldGameName="TP_FirstPersonBP",NewGameName="/Script/VRTemplate") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_FirstPersonBP",NewGameName="/Script/VRTemplate") +bSmoothFrameRate=False +GameViewportClientClassName=/Script/VRExpansionPlugin.VRGameViewportClient +NearClipPlane=2.000000 +LocalPlayerClassName=/Game/VRE/Core/CustomLocalPlayer.CustomLocalPlayer_C + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum + +[/Script/Engine.RendererSettings] +r.Nanite=0 +r.DefaultFeature.AmbientOcclusion=False +r.DefaultFeature.AmbientOcclusionStaticFraction=False +vr.InstancedStereo=True +r.SeparateTranslucency=0 +r.HZBOcclusion=0 +r.EyeAdaptionQuality=0 +r.SSR.MaxRoughness=0 +r.rhicmdbypass=0 +r.TiledReflectionEnvironmentMinimumCount=10 +r.ForwardShading=True +r.DefaultFeature.AntiAliasing=3 +r.AllowStaticLighting=True +r.DefaultFeature.MotionBlur=False +r.DefaultFeature.Bloom=True +r.DefaultFeature.AutoExposure=False +r.CustomDepth=1 +r.CustomDepthTemporalAAJitter=True +r.EarlyZPass=3 +r.EarlyZPassMovable=True +r.GBufferFormat=1 +r.Mobile.UseHWsRGBEncoding=True +r.MobileMSAA=4 +r.MobileHDR=False +r.AntiAliasingMethod=3 +r.Mobile.AntiAliasing=3 +vr.VRS.HMDFixedFoveationDynamic=False +vr.VRS.HMDFixedFoveationLevel=0 +vr.MobileMultiView=True + +[/Script/NavigationSystem.NavigationSystemV1] +bAutoCreateNavigationData=True +bAllowClientSideNavigation=True +bInitialBuildingLocked=False +bSkipAgentHeightCheckWhenPickingNavData=False +DataGatheringMode=Instant +bGenerateNavigationOnlyAroundNavigationInvokers=False +ActiveTilesUpdateInterval=1.000000 +DirtyAreasUpdateFreq=60.000000 + +[/Script/Engine.UserInterfaceSettings] +RenderFocusRule=NavigationOnly +DefaultCursor=None +TextEditBeamCursor=None +CrosshairsCursor=None +HandCursor=None +GrabHandCursor=None +GrabHandClosedCursor=None +SlashedCircleCursor=None +ApplicationScale=1.000000 +UIScaleRule=ShortestSide +CustomScalingRuleClass=None +UIScaleCurve=(EditorCurveData=(PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant,Keys=((Time=480.000000,Value=0.444000),(Time=720.000000,Value=0.666000),(Time=1080.000000,Value=1.000000),(Time=8640.000000,Value=8.000000)),DefaultValue=340282346638528859811704183484516925440.000000),ExternalCurve=None) + +[/Script/Engine.EndUserSettings] +bSendAnonymousUsageDataToEpic=False + +[/Script/Engine.CollisionProfile] +-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) +-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) +-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) +-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) +-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) +-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) +-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) +-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) +-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) ++Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) ++Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) ++Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) ++Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) ++Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) ++Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) ++Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) ++Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) ++Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,Name="VRTraceChannel",DefaultResponse=ECR_Block,bTraceType=True,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,Name="FloorTrace",DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel3,Name="PawnWalking",DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False) ++EditProfiles=(Name="PhysicsActor",CustomResponses=((Channel="VRTraceChannel"))) ++EditProfiles=(Name="BlockAllDynamic",CustomResponses=((Channel="VRTraceChannel"))) +-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") +-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") +-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") +-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") +-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") ++ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") ++ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") ++ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") ++ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") ++ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") +-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") +-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") +-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") +-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") ++CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") ++CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") ++CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") ++CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") + +[/Script/NavigationSystem.RecastNavMesh] +RuntimeGeneration=Dynamic + +[/Script/VRExpansionPlugin.VRGlobalSettings] +bUseGlobalLerpToHand=False +MinDistanceForLerp=10.000000 +LerpDuration=0.250000 +MinSpeedForLerp=100.000000 +MaxSpeedForLerp=500.000000 +LerpInterpolationMode=QuatInterp +bUseCurve=False +OptionalCurveToFollow=(EditorCurveData=(Keys=,DefaultValue=340282346638528859811704183484516925440.000000,PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant),ExternalCurve=None) +MaxCCDPasses=1 ++MeleeSurfaceSettings=(bSurfaceAllowsPenetration=True,BluntDamageScaler=1.000000,SharpDamageScaler=1.000000,StabVelocityScaler=1.000000,SurfaceType=SurfaceType2) ++MeleeSurfaceSettings=(bSurfaceAllowsPenetration=True,BluntDamageScaler=1.000000,SharpDamageScaler=1.000000,StabVelocityScaler=2.000000,SurfaceType=SurfaceType1) +VirtualStockSettings=(bUseDistanceBasedStockSnapping=True,StockSnapDistance=35.000000,StockSnapLerpThreshold=20.000000,StockLerpValue=0.000000,StockSnapOffset=(X=0.000000,Y=0.000000,Z=0.000000),bSmoothStockHand=True,SmoothingValueForStock=1.000000,StockHandSmoothing=(MinCutoff=0.100000,DeltaCutoff=10.000000,CutoffSlope=10.000000),bDebugDrawVirtualStock=False) +OneEuroMinCutoff=0.100000 +OneEuroCutoffSlope=10.000000 +OneEuroDeltaCutoff=10.000000 ++ControllerProfiles=(ControllerName="Oculus_Touch_Steam",SocketOffsetTransform=(Rotation=(X=0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=False,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) ++ControllerProfiles=(ControllerName="Oculus_Touch_Home",SocketOffsetTransform=(Rotation=(X=0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=False,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) ++ControllerProfiles=(ControllerName="Vive_Wands",SocketOffsetTransform=(Rotation=(X=0.000000,Y=0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=False,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) ++ControllerProfiles=(ControllerName="Windows_MR",SocketOffsetTransform=(Rotation=(X=0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=True,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) ++ControllerProfiles=(ControllerName="FPS_Pawn",SocketOffsetTransform=(Rotation=(X=0.000000,Y=-0.573576,Z=0.000000,W=0.819152),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=False,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) ++ControllerProfiles=(ControllerName="KnucklesEV2",SocketOffsetTransform=(Rotation=(X=-0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000)),bUseSeperateHandOffsetTransforms=True,SocketOffsetTransformRightHand=(Rotation=(X=0.000000,Y=-0.000000,Z=0.000000,W=1.000000),Translation=(X=0.000000,Y=0.000000,Z=0.000000),Scale3D=(X=1.000000,Y=1.000000,Z=1.000000))) +AngularDriveDampingScale=0.300000 +LinearDriveDampingScale=0.700000 +LinearDriveStiffnessScale=1.500000 +AngularDriveStiffnessScale=0.300000 +SoftLinearStiffnessScale=1.500000 +SoftLinearDampingScale=1.200000 +JointStiffness=1.000000 +bSetEngineChaosScalers=False +bLerpHybridWithSweepGrips=True +bHybridWithSweepUseDistanceBasedLerp=True +HybridWithSweepLerpDuration=0.660000 +bUseChaosTranslationScalers=False + +[/Script/Engine.PhysicsSettings] +DefaultGravityZ=-980.000000 +DefaultTerminalVelocity=4000.000000 +DefaultFluidFriction=0.300000 +SimulateScratchMemorySize=262144 +RagdollAggregateThreshold=4 +TriangleMeshTriangleMinAreaThreshold=5.000000 +bEnableShapeSharing=False +bEnablePCM=True +bEnableStabilization=False +bWarnMissingLocks=True +bEnable2DPhysics=False +PhysicErrorCorrection=(PingExtrapolation=0.100000,PingLimit=100.000000,ErrorPerLinearDifference=1.000000,ErrorPerAngularDifference=1.000000,MaxRestoredStateError=1.000000,MaxLinearHardSnapDistance=400.000000,PositionLerp=0.000000,AngleLerp=0.400000,LinearVelocityCoefficient=100.000000,AngularVelocityCoefficient=10.000000,ErrorAccumulationSeconds=0.500000,ErrorAccumulationDistanceSq=15.000000,ErrorAccumulationSimilarity=100.000000) +DefaultDegreesOfFreedom=Full3D +BounceThresholdVelocity=200.000000 +FrictionCombineMode=Average +RestitutionCombineMode=Average +MaxAngularVelocity=3600.000000 +MaxDepenetrationVelocity=0.000000 +ContactOffsetMultiplier=0.020000 +MinContactOffset=2.000000 +MaxContactOffset=8.000000 +bSimulateSkeletalMeshOnDedicatedServer=True +DefaultShapeComplexity=CTF_UseSimpleAndComplex +bSuppressFaceRemapTable=False +bSupportUVFromHitResults=False +bDisableActiveActors=False +bDisableKinematicStaticPairs=False +bDisableKinematicKinematicPairs=False +bDisableCCD=False +bEnableEnhancedDeterminism=False +AnimPhysicsMinDeltaTime=0.000000 +bSimulateAnimPhysicsAfterReset=False +MaxPhysicsDeltaTime=0.033333 +bSubstepping=True +bSubsteppingAsync=False +MaxSubstepDeltaTime=0.016667 +MaxSubsteps=6 +SyncSceneSmoothingFactor=0.000000 +InitialAverageFrameRate=0.016667 +PhysXTreeRebuildRate=10 ++PhysicalSurfaces=(Type=SurfaceType1,Name="Flesh") ++PhysicalSurfaces=(Type=SurfaceType2,Name="Wood") +DefaultBroadphaseSettings=(bUseMBPOnClient=False,bUseMBPOnServer=False,bUseMBPOuterBounds=False,MBPBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPOuterBounds=(Min=(X=0.000000,Y=0.000000,Z=0.000000),Max=(X=0.000000,Y=0.000000,Z=0.000000),IsValid=0),MBPNumSubdivs=2) +ChaosSettings=(DefaultThreadingModel=SingleThread,DedicatedThreadTickMode=VariableCapped,DedicatedThreadBufferMode=Double) +SolverOptions=(Iterations=8,CollisionPairIterations=1,PushOutIterations=4,CollisionPushOutPairIterations=4,CollisionMarginFraction=0.050000,CollisionMarginMax=10.000000,CollisionCullDistance=3.000000,CollisionMaxPushOutVelocity=100000.000000,JointPairIterations=1,JointPushOutPairIterations=1,ClusterConnectionFactor=1.000000,ClusterUnionConnectionType=DelaunayTriangulation,bGenerateCollisionData=False,CollisionFilterSettings=(FilterEnabled=False,MinMass=0.000000,MinSpeed=0.000000,MinImpulse=0.000000),bGenerateBreakData=False,BreakingFilterSettings=(FilterEnabled=False,MinMass=0.000000,MinSpeed=0.000000,MinVolume=0.000000),bGenerateTrailingData=False,TrailingFilterSettings=(FilterEnabled=False,MinMass=0.000000,MinSpeed=0.000000,MinVolume=0.000000),bGenerateContactGraph=True) + +[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings] +PackageName=com.YourCompany.[PROJECT] +StoreVersion=1 +StoreVersionOffsetArm64=0 +StoreVersionOffsetX8664=0 +ApplicationDisplayName= +VersionDisplayName=1.0 +MinSDKVersion=26 +TargetSDKVersion=33 +InstallLocation=InternalOnly +bEnableLint=False +bPackageDataInsideApk=False +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=True +bEnableBundle=False +bEnableUniversalAPK=False +bBundleABISplit=True +bBundleLanguageSplit=True +bBundleDensitySplit=True +ExtraApplicationSettings= +ExtraActivitySettings= +bAndroidVoiceEnabled=False +bEnableMulticastSupport=False +bPackageForMetaQuest=True +bRemoveOSIG=True +KeyStore= +KeyAlias= +KeyStorePassword= +KeyPassword= +bBuildForArm64=True +bBuildForX8664=False +bBuildForES31=False +bSupportsVulkan=True +bSupportsVulkanSM5=False +DebugVulkanLayerDirectory=(Path="") +bAndroidOpenGLSupportsBackbufferSampling=False +bDetectVulkanByDefault=True +bBuildWithHiddenSymbolVisibility=False +bDisableStackProtector=False +bDisableLibCppSharedDependencyValidation=False +bSaveSymbols=False +bStripShaderReflection=True +bEnableGooglePlaySupport=False +bUseGetAccounts=False +GamesAppID= +bEnableSnapshots=False +bSupportAdMob=True +AdMobAppID= +TagForChildDirectedTreatment=TAG_FOR_CHILD_DIRECTED_TREATMENT_UNSPECIFIED +TagForUnderAgeOfConsent=TAG_FOR_UNDER_AGE_OF_CONSENT_UNSPECIFIED +MaxAdContentRating=MAX_AD_CONTENT_RATING_G +AdMobAdUnitID= +GooglePlayLicenseKey= +GCMClientSenderID= +bShowLaunchImage=True +bAllowIMU=True +bAllowControllers=True +bBlockAndroidKeysOnControllers=False +bControllersBlockDeviceFeedback=False +AndroidAudio=Default +AudioSampleRate=44100 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=4 +AudioMaxChannels=0 +AudioNumSourceWorkers=0 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=0 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +SoundCueCookQualityIndex=-1 +MaxSampleRate=0.000000 +HighSampleRate=0.000000 +MedSampleRate=0.000000 +LowSampleRate=0.000000 +MinSampleRate=0.000000 +CompressionQualityModifier=0.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 + +[/Script/OculusHMD.OculusHMDRuntimeSettings] +FFRLevel=FFR_Medium +HandTrackingSupport=ControllersAndHands + +[/Script/LinuxTargetPlatform.LinuxTargetSettings] +SpatializationPlugin= +ReverbPlugin= +OcclusionPlugin= +SoundCueCookQualityIndex=-1 +-TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_ES31 + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=A20E6C4847E893682C3EDFA6620C12B0 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 + diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultGame.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultGame.ini new file mode 100644 index 0000000..60f5628 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultGame.ini @@ -0,0 +1,52 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=6652AB584B9973D2EDB8EE93165A687B +ProjectName=Virtual Reality BP Game Template +bStartInVR=True + +[/Script/Engine.GameNetworkManager] +TotalNetBandwidth=64000 +MaxDynamicBandwidth=15000 +MinDynamicBandwidth=4000 + +[/Script/Engine.GameSession] +bRequiresPushToTalk=true + +[/Script/UnrealEd.ProjectPackagingSettings] +Build=IfProjectHasCode +BuildConfiguration=PPBC_Development +BuildTarget= +StagingDirectory=(Path="E:/DESKTOP/VRExpPluginTestMulti") +FullRebuild=False +ForDistribution=False +IncludeDebugFiles=False +bExcludeMonolithicEngineHeadersInNativizedCode=False +UsePakFile=True +bUseIoStore=False +bGenerateChunks=False +bGenerateNoChunks=False +bChunkHardReferencesOnly=False +bForceOneChunkPerFile=False +MaxChunkSize=0 +bBuildHttpChunkInstallData=False +HttpChunkInstallDataDirectory=(Path="") +PakFileCompressionFormats= +PakFileAdditionalCompressionOptions= +HttpChunkInstallDataVersion= +IncludePrerequisites=True +IncludeAppLocalPrerequisites=False +bShareMaterialShaderCode=False +bDeterministicShaderCodeOrder=False +bSharedMaterialNativeLibraries=False +ApplocalPrerequisitesDirectory=(Path="") +IncludeCrashReporter=True +InternationalizationPreset=English +-CulturesToStage=en ++CulturesToStage=en +LocalizationTargetCatchAllChunkId=0 +bCookAll=False +bCookMapsOnly=True +bCompressed=True +bSkipEditorContent=False +bSkipMovies=False ++DirectoriesToAlwaysCook=(Path="/Game/VRE") + diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultGameplayTags.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultGameplayTags.ini new file mode 100644 index 0000000..6b861de --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultGameplayTags.ini @@ -0,0 +1,11 @@ + +[/Script/GameplayTags.GameplayTagsSettings] +ImportTagsFromConfig=False +WarnOnInvalidTags=True +FastReplication=True ++GameplayTagTableList=/Game/VRE/Core/DefaultGameplayTags.DefaultGameplayTags +NumBitsForContainerSize=6 +NetIndexFirstBitSegment=16 + + + diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultInput.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultInput.ini new file mode 100644 index 0000000..b4e868f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultInput.ini @@ -0,0 +1,204 @@ + + +[/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="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,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)) ++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="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="MotionController_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Left_Grip2Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_TriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip1Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MotionController_Right_Grip2Axis",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="OculusTouch_Left_Thumbstick",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_FaceButton1",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_FaceButton2",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_IndexPointing",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_ThumbUp",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_FaceButton1",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_FaceButton2",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_IndexPointing",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_ThumbUp",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,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="Daydream_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Daydream_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Daydream_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Daydream_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,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="OculusGo_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusGo_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusGo_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusGo_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)) +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=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 ++ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar) ++ActionMappings=(ActionName="TeleportLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=T) ++ActionMappings=(ActionName="TeleportRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_Thumbstick_Touch) ++ActionMappings=(ActionName="TeleportRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Trackpad_Touch) ++ActionMappings=(ActionName="TeleportRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Thumbstick_Click) ++ActionMappings=(ActionName="TeleportRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Thumbstick_Click) ++ActionMappings=(ActionName="AlternateGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Grip_Click) ++ActionMappings=(ActionName="AlternateGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_A_Click) ++ActionMappings=(ActionName="AlternateGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_A_Click) ++ActionMappings=(ActionName="AlternateGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Grip_Click) ++ActionMappings=(ActionName="AlternateGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Grip_Click) ++ActionMappings=(ActionName="AlternateGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Grip_Click) ++ActionMappings=(ActionName="AlternateGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_X_Click) ++ActionMappings=(ActionName="AlternateGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_A_Click) ++ActionMappings=(ActionName="PrimaryGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Trigger_Click) ++ActionMappings=(ActionName="PrimaryGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Trigger_Click) ++ActionMappings=(ActionName="PrimaryGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Grip_Click) ++ActionMappings=(ActionName="PrimaryGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_Grip_Force) ++ActionMappings=(ActionName="PrimaryGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Trigger_Click) ++ActionMappings=(ActionName="PrimaryGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Trigger_Click) ++ActionMappings=(ActionName="PrimaryGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Grip_Click) ++ActionMappings=(ActionName="PrimaryGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_Grip_Force) ++ActionMappings=(ActionName="TeleportLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Thumbstick_Click) ++ActionMappings=(ActionName="TeleportLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Thumbstick_Click) ++ActionMappings=(ActionName="TeleportLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_Thumbstick_Touch) ++ActionMappings=(ActionName="TeleportLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Trackpad_Touch) ++ActionMappings=(ActionName="LaserBeamRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Menu_Click) ++ActionMappings=(ActionName="LaserBeamRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_B_Click) ++ActionMappings=(ActionName="LaserBeamRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Thumbstick_Down) ++ActionMappings=(ActionName="LaserBeamRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_B_Click) ++ActionMappings=(ActionName="LaserBeamLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Menu_Click) ++ActionMappings=(ActionName="LaserBeamLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_B_Click) ++ActionMappings=(ActionName="LaserBeamLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Y_Click) ++ActionMappings=(ActionName="LaserBeamLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Thumbstick_Down) ++ActionMappings=(ActionName="LaserBeamLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=L) ++ActionMappings=(ActionName="LaserBeamRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=L) ++ActionMappings=(ActionName="PrimaryGripRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="PrimaryGripLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="UseHeldObjectLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Right_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Left_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Right_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=ValveIndex_Right_Trigger_Click) ++ActionMappings=(ActionName="UseHeldObjectLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="UseHeldObjectRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="TeleportRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=T) ++AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=W) ++AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=S) ++AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=D) ++AxisMappings=(AxisName="MoveRight",Scale=-1.000000,Key=A) ++AxisMappings=(AxisName="TurnRate",Scale=-1.000000,Key=Left) ++AxisMappings=(AxisName="TurnRate",Scale=1.000000,Key=Right) ++AxisMappings=(AxisName="LookUp",Scale=-1.000000,Key=MouseY) ++AxisMappings=(AxisName="Turn",Scale=1.000000,Key=MouseX) ++AxisMappings=(AxisName="TeleportDirectionUp",Scale=1.000000,Key=Gamepad_LeftY) ++AxisMappings=(AxisName="TeleportDirectionRight",Scale=1.000000,Key=Gamepad_LeftX) ++AxisMappings=(AxisName="MotionControllerThumbLeft_Y",Scale=1.000000,Key=ValveIndex_Left_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbLeft_Y",Scale=1.000000,Key=Vive_Left_Trackpad_Y) ++AxisMappings=(AxisName="MotionControllerThumbLeft_Y",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbLeft_Y",Scale=1.000000,Key=MixedReality_Left_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbLeft_X",Scale=1.000000,Key=ValveIndex_Left_Thumbstick_X) ++AxisMappings=(AxisName="MotionControllerThumbLeft_X",Scale=1.000000,Key=Vive_Left_Trackpad_X) ++AxisMappings=(AxisName="MotionControllerThumbLeft_X",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_X) ++AxisMappings=(AxisName="MotionControllerThumbLeft_X",Scale=1.000000,Key=MixedReality_Left_Thumbstick_X) ++AxisMappings=(AxisName="MotionControllerThumbRight_Y",Scale=1.000000,Key=ValveIndex_Right_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbRight_Y",Scale=1.000000,Key=Vive_Right_Trackpad_Y) ++AxisMappings=(AxisName="MotionControllerThumbRight_Y",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbRight_Y",Scale=1.000000,Key=MixedReality_Right_Thumbstick_Y) ++AxisMappings=(AxisName="MotionControllerThumbRight_X",Scale=1.000000,Key=ValveIndex_Right_Thumbstick_X) ++AxisMappings=(AxisName="MotionControllerThumbRight_X",Scale=1.000000,Key=Vive_Right_Trackpad_X) ++AxisMappings=(AxisName="MotionControllerThumbRight_X",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_X) ++AxisMappings=(AxisName="MotionControllerThumbRight_X",Scale=1.000000,Key=MixedReality_Right_Thumbstick_X) ++AxisMappings=(AxisName="ControllerMovementLeft",Scale=1.000000,Key=Vive_Left_Trackpad_Click) ++AxisMappings=(AxisName="ControllerMovementLeft",Scale=1.000000,Key=ValveIndex_Left_Thumbstick_Touch) ++AxisMappings=(AxisName="ControllerMovementLeft",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_Touch) ++AxisMappings=(AxisName="ControllerMovementLeft",Scale=1.000000,Key=MixedReality_Left_Thumbstick_Click) ++AxisMappings=(AxisName="ControllerMovementRight",Scale=1.000000,Key=ValveIndex_Right_Thumbstick_Touch) ++AxisMappings=(AxisName="ControllerMovementRight",Scale=1.000000,Key=Vive_Right_Trackpad_Click) ++AxisMappings=(AxisName="ControllerMovementRight",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_Touch) ++AxisMappings=(AxisName="ControllerMovementRight",Scale=1.000000,Key=MixedReality_Right_Thumbstick_Click) +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + +[/Script/OpenXRInput.OpenXRInputSettings] +MappableInputConfig=/Game/VRE/Input/VREInputConfig.VREInputConfig + diff --git a/VIRTUOS_ExpansionPluginTests/Config/DefaultScalability.ini b/VIRTUOS_ExpansionPluginTests/Config/DefaultScalability.ini new file mode 100644 index 0000000..1107172 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/DefaultScalability.ini @@ -0,0 +1,90 @@ + +[AntiAliasingQuality@0] +r.PostProcessAAQuality=0 + +[AntiAliasingQuality@1] +r.PostProcessAAQuality=2 + +[AntiAliasingQuality@2] +r.PostProcessAAQuality=3 + +[AntiAliasingQuality@3] +r.PostProcessAAQuality=3 + +;----------------------------------------------------------------------------------------------------------------- + +[PostProcessQuality@0] +r.MotionBlurQuality=0 +r.AmbientOcclusionLevels=0 +r.LensFlareQuality=0 +r.SceneColorFringeQuality=0 +r.DepthOfFieldQuality=0 +r.BloomQuality=1 +r.FastBlurThreshold=0 + +[PostProcessQuality@1] +r.MotionBlurQuality=0 +r.AmbientOcclusionLevels=0 +r.LensFlareQuality=0 +r.SceneColorFringeQuality=0 +r.DepthOfFieldQuality=0 +r.BloomQuality=1 +r.FastBlurThreshold=0 + +[PostProcessQuality@2] +r.MotionBlurQuality=0 +r.AmbientOcclusionLevels=0 +r.LensFlareQuality=0 +r.SceneColorFringeQuality=0 +r.DepthOfFieldQuality=0 +r.BloomQuality=1 +r.FastBlurThreshold=0 + +[PostProcessQuality@3] +r.MotionBlurQuality=0 +r.AmbientOcclusionLevels=0 +r.LensFlareQuality=0 +r.SceneColorFringeQuality=0 +r.DepthOfFieldQuality=0 +r.BloomQuality=1 +r.FastBlurThreshold=0 + +;----------------------------------------------------------------------------------------------------------------- + +[TextureQuality@0] +r.MaxAnisotropy=0 + +[TextureQuality@1] +r.MaxAnisotropy=2 + +[TextureQuality@2] +r.MaxAnisotropy=4 + +[TextureQuality@3] +r.MaxAnisotropy=8 + +;----------------------------------------------------------------------------------------------------------------- + +[EffectsQuality@0] +r.TranslucencyVolumeBlur=0 +r.TranslucencyLightingVolumeDim=4 +r.SceneColorFormat=2 +r.SSR.Quality=0 + +[EffectsQuality@1] +r.TranslucencyVolumeBlur=0 +r.TranslucencyLightingVolumeDim=4 +r.SceneColorFormat=2 +r.SSR.Quality=0 + +[EffectsQuality@2] +r.TranslucencyVolumeBlur=0 +r.TranslucencyLightingVolumeDim=4 +r.SceneColorFormat=2 +r.SSR.Quality=0 + +[EffectsQuality@3] +r.TranslucencyVolumeBlur=0 +r.TranslucencyLightingVolumeDim=4 +r.SceneColorFormat=2 +r.SSR.Quality=0 \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/HoloLens/HoloLensEngine.ini b/VIRTUOS_ExpansionPluginTests/Config/HoloLens/HoloLensEngine.ini new file mode 100644 index 0000000..fa8a9a8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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.19041.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/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/gamepad.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/gamepad.json new file mode 100644 index 0000000..fff7db7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/holographic_controller.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/holographic_controller.json new file mode 100644 index 0000000..b0267cd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/holographic_controller.json @@ -0,0 +1,229 @@ +{ + "name": "Default bindings for MixedReality", + "controller_type": "holographic_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "click": + { + "output": "/actions/main/in/TeleportLeft" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "click": + { + "output": "/actions/main/in/TeleportRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripRight" + } + } + }, + { + "mode": "dpad", + "path": "/user/hand/right/input/joystick", + "parameters": + { + "sub_mode": "touch" + }, + "inputs": + { + "south": + { + "output": "/actions/main/in/LaserBeamRight" + } + } + }, + { + "mode": "dpad", + "path": "/user/hand/left/input/joystick", + "parameters": + { + "sub_mode": "touch" + }, + "inputs": + { + "south": + { + "output": "/actions/main/in/LaserBeamLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionLEft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionRight" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementLeft axis" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementRight axis" + } + } + } + ], + "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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/indexhmd.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/indexhmd.json new file mode 100644 index 0000000..91f92fd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/knuckles.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/knuckles.json new file mode 100644 index 0000000..efee109 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/knuckles.json @@ -0,0 +1,200 @@ +{ + "action_manifest_version" : 0, + "alias_info" : {}, + "app_key" : "application.generated.ue.virtual-reality-bp-game-template-14627614.ue4editor-win64-debuggame.exe", + "bindings" : { + "/actions/main" : { + "haptics" : [ + { + "output" : "/actions/main/out/vibrateleft", + "path" : "/user/hand/left/output/haptic" + }, + { + "output" : "/actions/main/out/vibrateright", + "path" : "/user/hand/right/output/haptic" + } + ], + "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" + } + ], + "sources" : [ + { + "inputs" : { + "east" : { + "output" : "/actions/main/in/teleportleft" + }, + "north" : { + "output" : "/actions/main/in/teleportleft" + }, + "south" : { + "output" : "/actions/main/in/teleportleft" + }, + "west" : { + "output" : "/actions/main/in/TeleportLeft" + } + }, + "mode" : "dpad", + "parameters" : { + "sub_mode" : "touch" + }, + "path" : "/user/hand/left/input/thumbstick" + }, + { + "inputs" : { + "east" : { + "output" : "/actions/main/in/teleportright" + }, + "north" : { + "output" : "/actions/main/in/teleportright" + }, + "south" : { + "output" : "/actions/main/in/teleportright" + }, + "west" : { + "output" : "/actions/main/in/teleportright" + } + }, + "mode" : "dpad", + "parameters" : { + "sub_mode" : "touch" + }, + "path" : "/user/hand/right/input/thumbstick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/AlternateGripRight" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/a" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/AlternateGripLeft" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/a" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/LaserBeamRight" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/b" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/LaserBeamLeft" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/b" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/UseHeldObjectLeft" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/UseHeldObjectRight" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/trigger" + }, + { + "inputs" : { + "position" : { + "output" : "/actions/main/in/motioncontrollerthumbleft_x,motioncontrollerthumbleft_y x y_axis2d" + }, + "touch" : { + "output" : "/actions/main/in/controllermovementleft axis" + } + }, + "mode" : "joystick", + "parameters" : { + "deadzone_pct" : "10" + }, + "path" : "/user/hand/left/input/thumbstick" + }, + { + "inputs" : { + "position" : { + "output" : "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + }, + "touch" : { + "output" : "/actions/main/in/controllermovementright axis" + } + }, + "mode" : "joystick", + "parameters" : { + "deadzone_pct" : "10" + }, + "path" : "/user/hand/right/input/thumbstick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/primarygripleft" + } + }, + "mode" : "button", + "parameters" : { + "click_activate_threshold" : "1", + "click_deactivate_threshold" : "1" + }, + "path" : "/user/hand/left/input/grip" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/main/in/primarygripright" + } + }, + "mode" : "button", + "parameters" : { + "click_activate_threshold" : "1", + "click_deactivate_threshold" : "1" + }, + "path" : "/user/hand/right/input/grip" + } + ] + } + }, + "category" : "steamvr_input", + "controller_type" : "knuckles", + "description" : "Virtual Reality BP Game Template-14627614", + "name" : "Default bindings for ValveIndex", + "options" : {}, + "simulated_actions" : [] +} diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/oculus_touch.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/oculus_touch.json new file mode 100644 index 0000000..22353e3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/oculus_touch.json @@ -0,0 +1,221 @@ +{ + "name": "Default bindings for OculusTouch", + "controller_type": "oculus_touch", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportLeft" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/a", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/x", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/b", + "inputs": + { + "touch": + { + "output": "/actions/main/in/LaserBeamRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/y", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionLEft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionRight" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementLeft axis" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementRight axis" + } + } + } + ], + "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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/rift.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/rift.json new file mode 100644 index 0000000..d1af9fe --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/steamvr_manifest.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/steamvr_manifest.json new file mode 100644 index 0000000..63a7eb7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/steamvr_manifest.json @@ -0,0 +1,270 @@ +{ + "actions": [ + { + "name": "/actions/main/in/controllerleft", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/controllerright", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special1", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special2", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special3", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special4", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special5", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special6", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special7", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/special8", + "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/in/open_console", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/main/out/vibrateleft", + "type": "vibration", + "requirement": "optional" + }, + { + "name": "/actions/main/out/vibrateright", + "type": "vibration", + "requirement": "optional" + }, + { + "name": "/actions/main/in/TeleportLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/TeleportRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/AlternateGripRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/AlternateGripLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/PrimaryGripLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/PrimaryGripRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/LaserBeamRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/LaserBeamLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/UseHeldObjectLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/UseHeldObjectRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d", + "type": "vector2" + }, + { + "name": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d", + "type": "vector2" + }, + { + "name": "/actions/main/in/ControllerMovementLeft axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/ControllerMovementRight axis", + "type": "vector1" + } + ], + "action_sets": [ + { + "name": "/actions/main", + "usage": "leftright" + } + ], + "default_bindings": [ + { + "controller_type": "gamepad", + "binding_url": "gamepad.json" + }, + { + "controller_type": "holographic_controller", + "binding_url": "holographic_controller.json" + }, + { + "controller_type": "indexhmd", + "binding_url": "indexhmd.json" + }, + { + "controller_type": "knuckles", + "binding_url": "knuckles.json" + }, + { + "controller_type": "oculus_touch", + "binding_url": "oculus_touch.json" + }, + { + "controller_type": "rift", + "binding_url": "rift.json" + }, + { + "controller_type": "vive", + "binding_url": "vive.json" + }, + { + "controller_type": "vive_controller", + "binding_url": "vive_controller.json" + }, + { + "controller_type": "vive_cosmos_controller", + "binding_url": "vive_cosmos_controller.json" + }, + { + "controller_type": "vive_pro", + "binding_url": "vive_pro.json" + }, + { + "controller_type": "vive_tracker_camera", + "binding_url": "vive_tracker_camera.json" + }, + { + "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": "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_camera", + "binding_url": "vive_tracker_camera.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/special1": "Special 1 [Tracker]", + "/actions/main/in/special2": "Special 2 [Tracker]", + "/actions/main/in/special3": "Special 3 [Tracker]", + "/actions/main/in/special4": "Special 4 [Tracker]", + "/actions/main/in/special5": "Special 5 [Tracker]", + "/actions/main/in/special6": "Special 6 [Tracker]", + "/actions/main/in/special7": "Special 7 [Tracker]", + "/actions/main/in/special8": "Special 8 [Tracker]", + "/actions/main/in/skeletonleft": "Skeleton (Left)", + "/actions/main/in/skeletonright": "Skeleton (Right)", + "/actions/main/in/open_console": "Open Console", + "/actions/main/out/vibrateleft": "Haptic (Left)", + "/actions/main/out/vibrateright": "Haptic (Right)", + "/actions/main/in/TeleportLeft": "TeleportLeft", + "/actions/main/in/TeleportRight": "TeleportRight", + "/actions/main/in/AlternateGripRight": "AlternateGripRight", + "/actions/main/in/AlternateGripLeft": "AlternateGripLeft", + "/actions/main/in/PrimaryGripLeft": "PrimaryGripLeft", + "/actions/main/in/PrimaryGripRight": "PrimaryGripRight", + "/actions/main/in/LaserBeamRight": "LaserBeamRight", + "/actions/main/in/LaserBeamLeft": "LaserBeamLeft", + "/actions/main/in/UseHeldObjectLeft": "UseHeldObjectLeft", + "/actions/main/in/UseHeldObjectRight": "UseHeldObjectRight", + "/actions/main/in/TriggerInteractionLEft": "TriggerInteractionLEft", + "/actions/main/in/TriggerInteractionRight": "TriggerInteractionRight", + "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d": "MotionControllerThumbLeft", + "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d": "MotionControllerThumbRight", + "/actions/main/in/ControllerMovementLeft axis": "ControllerMovementLeft", + "/actions/main/in/ControllerMovementRight axis": "ControllerMovementRight", + "/actions/main": "Main Game Actions" + } + ] +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive.json new file mode 100644 index 0000000..2f8cb4e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_controller.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_controller.json new file mode 100644 index 0000000..f87239d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_controller.json @@ -0,0 +1,221 @@ +{ + "name": "Default bindings for Vive", + "controller_type": "vive_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "trackpad", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportLeft" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/application_menu", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/application_menu", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionLEft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionRight" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementLeft axis" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementRight axis" + } + } + } + ], + "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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_cosmos_controller.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_cosmos_controller.json new file mode 100644 index 0000000..1b2ac33 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_cosmos_controller.json @@ -0,0 +1,221 @@ +{ + "name": "Default bindings for Cosmos", + "controller_type": "vive_cosmos_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportLeft" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/b", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/y", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionLEft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionRight" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementLeft axis" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementRight axis" + } + } + } + ], + "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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_pro.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_pro.json new file mode 100644 index 0000000..5a7ce10 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/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": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_tracker_camera.json b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_tracker_camera.json new file mode 100644 index 0000000..d25fce8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Config/SteamVRBindings/vive_tracker_camera.json @@ -0,0 +1,232 @@ +{ + "name": "Default bindings for Vive Trackers", + "controller_type": "vive_tracker_camera", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "trackpad", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportLeft" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "touch": + { + "output": "/actions/main/in/TeleportRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/AlternateGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/PrimaryGripRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/application_menu", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/application_menu", + "inputs": + { + "click": + { + "output": "/actions/main/in/LaserBeamLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectLeft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/UseHeldObjectRight" + } + } + }, + { + "mode": "button", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionLEft" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/TriggerInteractionRight" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbLeft_X,MotionControllerThumbLeft_Y X Y_axis2d" + } + } + }, + { + "mode": "trackpad", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "position": + { + "output": "/actions/main/in/MotionControllerThumbRight_X,MotionControllerThumbRight_Y X Y_axis2d" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/left/input/trackpad", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementLeft axis" + } + } + }, + { + "mode": "scalar_constant", + "path": "/user/hand/right/input/trackpad", + "inputs": + { + "value": + { + "output": "/actions/main/in/ControllerMovementRight axis" + } + } + } + ], + "poses": [ + { + "output": "/actions/main/in/special1", + "path": "/user/hand/left/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special2", + "path": "/user/hand/right/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special3", + "path": "/user/hand/left/pose/front", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special4", + "path": "/user/hand/right/pose/front", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special5", + "path": "/user/hand/left/pose/frontandrolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special6", + "path": "/user/hand/right/pose/frontandrolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special7", + "path": "/user/hand/left/pose/pistolgrip", + "requirement": "optional" + }, + { + "output": "/actions/main/in/special8", + "path": "/user/hand/right/pose/pistolgrip", + "requirement": "optional" + } + ] + } + }, + "description": "Virtual Reality BP Game Template-11100242" +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_Teleport_Controller.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_Teleport_Controller.uasset new file mode 100644 index 0000000..684f55a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_Teleport_Controller.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f1ed57763e5651c2e37eecb9d5ebb96212e22f8e33780bf9a6389da3537af2d +size 1187934 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_VRCharacter.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_VRCharacter.uasset new file mode 100644 index 0000000..39fdbf7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BP_VRCharacter.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f65066d1020d466d37a6e178305693752f8de27d2ff857cb62dfd53363f67e6d +size 5196567 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BasicShapeMaterialTrans.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BasicShapeMaterialTrans.uasset new file mode 100644 index 0000000..3c801d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/BasicShapeMaterialTrans.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58f1201bcbc50d575bdffa78d222f112da953bcfbc80856857a2f91165047a63 +size 31685 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/DefaultTextMaterialOpaque2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/DefaultTextMaterialOpaque2.uasset new file mode 100644 index 0000000..ba3b229 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/DefaultTextMaterialOpaque2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:635c9b00ebe64ad76ce2128397d65c75469984f909f1047a0f6571cbebcb61ed +size 53693 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/FPS_VRCharacter.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/FPS_VRCharacter.uasset new file mode 100644 index 0000000..602c8c8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/FPS_VRCharacter.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b301fc5f0b1876baa0845e45e395d2a17c176174580d0ea95a530791653b17 +size 317409 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/GripEnum.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/GripEnum.uasset new file mode 100644 index 0000000..d26bf51 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/GripEnum.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:beff711e061a3bc2b8d308504cd1ccb6b9e44c702afdbfdbe18cc9297835a02c +size 2693 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/LaserBeamSplineMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/LaserBeamSplineMat.uasset new file mode 100644 index 0000000..993462c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/LaserBeamSplineMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ce59b72a756fba9cacba821f446f5ad3776046c9c0875d06183811ce5a71c09 +size 21948 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_CanGrab.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_CanGrab.uasset new file mode 100644 index 0000000..9b78d92 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_CanGrab.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e2da68c5ea6e7a3ef662a7ac8c30c5d843549bd17e858c6a0fee4f7c6d38f04 +size 182385 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Grab.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Grab.uasset new file mode 100644 index 0000000..d0350f3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Grab.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e64c362bad08b3cf6807bdf7ddf3bd8d09fcdd6c8d5f4c677d08ed65d9b659cc +size 179613 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Open.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Open.uasset new file mode 100644 index 0000000..52520d2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/MannequinHand_Right_Open.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df5662a655d4a1ed8c8b53eadc0efb7c10641d1addf84f9d18faa0ee71697de0 +size 181442 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightGrip_BS.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightGrip_BS.uasset new file mode 100644 index 0000000..fe07cb3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightGrip_BS.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97afb117c4d48760302fe617cbb826dbf122c798235f2260e084c90b94104e4a +size 78621 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightHand_AnimBP.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightHand_AnimBP.uasset new file mode 100644 index 0000000..7766563 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Animations/RightHand_AnimBP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ace9b2a7b2461c05cf617151fe17d0e0c077772a18f2dc36c127373faa300d9 +size 89078 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_HandMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_HandMat.uasset new file mode 100644 index 0000000..2143b3c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_HandMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5408d88d682b1d8ac4a19f308d8763b8b6831e082656a9610799d2f997c46b28 +size 126834 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_Body.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_Body.uasset new file mode 100644 index 0000000..dd0a558 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_Body.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb1221b071760d9dcfba75f0571f49608c921310cfa2f488002a243198b8f1a5 +size 29782 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_ChestLogo.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_ChestLogo.uasset new file mode 100644 index 0000000..dc87ca7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/M_UE4Man_ChestLogo.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfc7020cafbeee6767b0e78ed2d03cb0a5fa133357149120ed5c8bfe394e57df +size 9894 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset new file mode 100644 index 0000000..d10ffb4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ab6d666fd644894fa774fa9dda8eb352fd1bce6d6c31f7a30d1b117bb7aef33 +size 87003 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset new file mode 100644 index 0000000..464c92a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc6c2715548627e400f396bbb3b84ac8df814822e2f07a35cb1617e760d8dc76 +size 100213 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset new file mode 100644 index 0000000..8470a0a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:290cf62fb29f6dfe8881596cf940b3ca128c45d56fe81707c08bb3c034ebd781 +size 11203 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset new file mode 100644 index 0000000..af20b4f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd107a851b46f797ae0f07e68119092f47006e0a1b3a74fde58aac1baa45fc06 +size 87500 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01.uasset new file mode 100644 index 0000000..e7d7ef1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb1c60928051c5bf2e48e56c6f6bf5d9664e1e0fa977d8968c946bfb8531ae5d +size 395367 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset new file mode 100644 index 0000000..a749813 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5d6d0c10c263d396cbe623561dd9f590378f4219f13256929aba78f2e7f9259 +size 404256 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset new file mode 100644 index 0000000..933e548 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f7fa9ef137740de52f259ea1d53453e6d739a74e5cb77b6f110812d176e90a +size 428993 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset new file mode 100644 index 0000000..79c8dce --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58ced35291d53058126de2e835460a0cde4993d2dd3cb364eeb026fb944cb1d9 +size 358491 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right.uasset new file mode 100644 index 0000000..518d701 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:444309d9a0f79a3ecb48e66d13e9ab08d83daecd9d28a34fd92c34935cbff7f7 +size 555575 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_PhysicsAsset.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_PhysicsAsset.uasset new file mode 100644 index 0000000..8dae0fa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_PhysicsAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:384dbce181235b77e56d1915661c6a35002b1fa284c312708272c2270f0efda7 +size 8200 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_Skeleton.uasset new file mode 100644 index 0000000..f76f0fa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/MannequinHand_Right_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e5e73f9c213323ace75dd9b87f27e79a7c739abb44d2885c0b4e916a25e9519 +size 10699 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin.uasset new file mode 100644 index 0000000..ccf6e3a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7dc6c0ff7dbed333463595c609151c34154a746146649e69e943c8178f54b76d +size 5583019 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin_PhysicsAsset.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin_PhysicsAsset.uasset new file mode 100644 index 0000000..3eb1695 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/SK_Mannequin_PhysicsAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ed75a8aaee902d169ddb980125d9dbeb929851ef33009693a136dc3928211e6 +size 46351 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton.uasset new file mode 100644 index 0000000..c0b6225 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9012982f60619c314d67bd9477e6bc3c98d90b1864e5532ca7dd9f3147c8bf6b +size 21571 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/models_hands_vr_glove_vmat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/models_hands_vr_glove_vmat.uasset new file mode 100644 index 0000000..19f514a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/models_hands_vr_glove_vmat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a11e85324d5097e5a93563d4d706ec763ad4e6c8a9a0006be35776185476cf4f +size 122076 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color.uasset new file mode 100644 index 0000000..7930bf9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f8c0dc8bc0cb9187b2af5b39ad5ffa58054e02d90954b15d7db5be165ce1a69 +size 1561204 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color_red.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color_red.uasset new file mode 100644 index 0000000..834142c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_color_red.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f6bc42a6e8c95b19afef0529046d803ad106cae5390790a0a0f2bd9f466b795 +size 1444461 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model.uasset new file mode 100644 index 0000000..4850151 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4dad86a8d893b632636892641163cf90ffb6f39733269b15b28b2c4d1697c45 +size 599145 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model_Skeleton.uasset new file mode 100644 index 0000000..6e081c9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_left_model_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ea51de7b170b0ef094cbfc17cbc576507fcce7697e8e16c8733ba856b68103a +size 10570 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_normal.uasset new file mode 100644 index 0000000..92f4fd6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:959ae0ad037205b36f8521a70359b45ae6ed51875cec9eb5f61036a5dbd0a754 +size 1825248 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model.uasset new file mode 100644 index 0000000..433a14c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:add21dfee93998d0e96661bdd12ae44d51b042c54344348c96e524d9444b4da1 +size 598535 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model_Skeleton.uasset new file mode 100644 index 0000000..78be17e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Mesh/vr_glove_right_model_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29f881745fdf7f236e29dcc7558ba940108c415a982e134a7f31f11df6b9b78a +size 10574 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4Man_Logo_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4Man_Logo_N.uasset new file mode 100644 index 0000000..7a2a6dd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4Man_Logo_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9492b25939ff1d478e15d1b7d0f1234ff4237a440705fd8b60288c0dd16f22e +size 137426 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_LOGO_CARD.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_LOGO_CARD.uasset new file mode 100644 index 0000000..f783b6f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_LOGO_CARD.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6badd78cd1054104de8a0acef1aec56fdefd0c57ca56f89802bf8f4581f33d9 +size 70878 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_MAT_MASKA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_MAT_MASKA.uasset new file mode 100644 index 0000000..0dd9b8d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_MAT_MASKA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07842eb857d733ec1ba3ac29949afab7307f76f48051503a07d10e04219a1546 +size 211715 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin_MAT_MASKA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin_MAT_MASKA.uasset new file mode 100644 index 0000000..edd80a9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin_MAT_MASKA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:470dd9cda45e6e2c75a57a851a8748ac3be1e8e313460fc3cba1b49874a0f3f4 +size 211755 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__norm.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__norm.uasset new file mode 100644 index 0000000..24eb036 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__norm.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ed6047f58c4cd64dc8942ceeaab7d315fa94906148d5d6785b005b6b9f923a +size 5427379 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__normals.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__normals.uasset new file mode 100644 index 0000000..5d5dff8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequin/Character/Textures/UE4_Mannequin__normals.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4707f8ef7073631612811dbd4a21beeadf7bb5c2a7809961d8dc3509de1dac0 +size 5427391 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Manny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Manny.uasset new file mode 100644 index 0000000..63d77ff --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Manny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3ffd71bccec69abff163356446a7980111452b030a7a3dfee461424c2cde74d +size 370976 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Quinn.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Quinn.uasset new file mode 100644 index 0000000..1639c5a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/ABP_Quinn.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:138a910497e5c70007ac3d8a0e4993c11398c7c6fd9d2544f4189f9f15f5cda2 +size 40139 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/BS_MM_WalkRun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/BS_MM_WalkRun.uasset new file mode 100644 index 0000000..4bc0329 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/BS_MM_WalkRun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8acae4dcedf36a5f5b136e061077a1c3ed992abbde86d358577dd4c3fca45dad +size 9153 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Fall_Loop.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Fall_Loop.uasset new file mode 100644 index 0000000..3e40e51 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Fall_Loop.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d51e888c1eefd69b5232e4cc6334ed6686956de6ac17e69ae6e4d1f57b7e6350 +size 659645 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Idle.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Idle.uasset new file mode 100644 index 0000000..5accf35 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Idle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7aaf7ac0774daebe07de85da640c6507245067737734f2ba6bb12a62949789c +size 876005 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Jump.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Jump.uasset new file mode 100644 index 0000000..f20cbf5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Jump.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3540f4d3626cdc7e93dca2a3b867f395ceebb963f99d57b73dd7ae445e1c684 +size 252393 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Land.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Land.uasset new file mode 100644 index 0000000..a22edbe --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Land.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bb628219d7e653767b30cf86df62e227f1b8daf2dbfa6b852b8b905c87dab50 +size 261766 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Run_Fwd.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Run_Fwd.uasset new file mode 100644 index 0000000..93f8183 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Run_Fwd.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c93ea1786347f2d3b52014f3e4d6f7a691726c333cc572721c740b91336380f +size 271611 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_Fwd.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_Fwd.uasset new file mode 100644 index 0000000..3fd08d2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_Fwd.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:243e8534ffb8348f257e1e390b0acaffbced581987771c0564f01b5c4da241f5 +size 361158 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_InPlace.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_InPlace.uasset new file mode 100644 index 0000000..6983ba1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Manny/MM_Walk_InPlace.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c4f94a99357d4b8b8efb1ff23769a39932518b3a4e343db9222e9a29b63149a +size 570800 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/BS_MF_Unarmed_WalkRun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/BS_MF_Unarmed_WalkRun.uasset new file mode 100644 index 0000000..9352b9e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/BS_MF_Unarmed_WalkRun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28fef93862e8758129b5f7ee2997d2e71d27cf5cabace52eb5d6e228343b916f +size 9193 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Idle.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Idle.uasset new file mode 100644 index 0000000..df3b1fe --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Idle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:505d8e71cbe6761c0bb62af210ebf534ec253c8356d3984e23febf80329d2716 +size 880797 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Run_Fwd.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Run_Fwd.uasset new file mode 100644 index 0000000..809e666 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Run_Fwd.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29b35e9ceeb7cca02d9f920db519472b758acccd78e6f2a846ebbbd1a3c0dcac +size 392562 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Walk_Fwd.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Walk_Fwd.uasset new file mode 100644 index 0000000..29dbfce --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Animations/Quinn/MF_Walk_Fwd.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a52b767af2e9a9f8cee083466a068f7b29a4d30a0e7428b14b61dbd1d04263f5 +size 411429 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/CA_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/CA_Mannequin.uasset new file mode 100644 index 0000000..c2cc1f0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/CA_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f845106db23168ccf6f1638185bc7b5c1e458722cf1a757d14e550ca81f6f85 +size 4968 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ChromaticCurve.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ChromaticCurve.uasset new file mode 100644 index 0000000..102734e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ChromaticCurve.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e16b429037def385b9477be63f9c1dbe44eba465228660450b089bf8d60d259f +size 6435 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_Diffraction.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_Diffraction.uasset new file mode 100644 index 0000000..4dc7f3b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_Diffraction.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23f1ece80ac62814a694cf50fd7854b8476cb6f2242cfc265012b1201d350c85 +size 32059 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_logo3layers.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_logo3layers.uasset new file mode 100644 index 0000000..593cf82 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/MF_logo3layers.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:913352fe76b0c0c44217ca76babf8ecae15e590e315470d98815a2e00f98173f +size 56015 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ML_BaseColorFallOff.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ML_BaseColorFallOff.uasset new file mode 100644 index 0000000..01b6b66 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Functions/ML_BaseColorFallOff.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d54d6bc865c113b78654dc8497173ff83863fbd483f4cd91c1e933cdbf016e82 +size 13739 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_01.uasset new file mode 100644 index 0000000..dc46ed4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14719191a97abe54593ff2c8ac936121055d3fdd5f3c645e5b7100fbb0ab396c +size 21533 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_02.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_02.uasset new file mode 100644 index 0000000..f29dbb0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Manny/MI_Manny_02.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e67d413b3eecbadd402ce3f24759cbb1d08367194c7cf7a977764218a8690d +size 24564 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_01.uasset new file mode 100644 index 0000000..486c014 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c66658b6032f46d60bb4271571b0954022a336a78c55cd02af4f7ebaeeae7fe3 +size 19578 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_02.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_02.uasset new file mode 100644 index 0000000..7d2b736 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/Instances/Quinn/MI_Quinn_02.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0aa8f6da31dd346b9e237f3221707d210a3a3df61b1e47b5a0f92b7844afa32d +size 24923 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/M_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/M_Mannequin.uasset new file mode 100644 index 0000000..c02c488 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Materials/M_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:102c18dfb1698b567b0822a51c733ed6fd2c24b93bec07a6e9d9b940a5e38558 +size 83288 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/Mannequin_LODSettings.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/Mannequin_LODSettings.uasset new file mode 100644 index 0000000..c1b9fac --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/Mannequin_LODSettings.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77d9c5e7fccd2887c53cdecf10234b85d2ba500e184b8c10774b4ade25edae3a +size 19131 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny.uasset new file mode 100644 index 0000000..183b2c3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2872a0f2c074351aa06902e958a88acbbad3cb8bf392abb3c1e263ad0e746dd1 +size 34534742 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny_Simple.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny_Simple.uasset new file mode 100644 index 0000000..af9abb0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Manny_Simple.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fd6d74751113f7121ed50b0186e08eb173e2cb5d01b0f617d59cf67b00f6ef8 +size 18526982 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn.uasset new file mode 100644 index 0000000..2dfcd8a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc51eccd57660addda0e480d3db60e57e623e2e0cfb0af5e867a1d53b361b238 +size 36495758 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn_Simple.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn_Simple.uasset new file mode 100644 index 0000000..91d07a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SKM_Quinn_Simple.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2febd11df2ab7e875dbc44172e360e3779e84952954b5ff5a3a8d19bfd481d7b +size 19371456 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SK_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SK_Mannequin.uasset new file mode 100644 index 0000000..2fb9db8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Meshes/SK_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64245b653cdcbdf1c36f1f24bc2eea411505bf68f2ceb430a362bf4a9263630c +size 160772 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Manny_PostProcess.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Manny_PostProcess.uasset new file mode 100644 index 0000000..0003778 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Manny_PostProcess.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76c17aacee9867a44c09c5ccbd7c7e618a7aac55152ceec8b9a2405e8925c565 +size 463186 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Quinn_PostProcess.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Quinn_PostProcess.uasset new file mode 100644 index 0000000..eb951eb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/ABP_Quinn_PostProcess.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d805c548607edd93e1525f32f62be35fc0bc3b49288f0f28f75d475d733aa25 +size 462984 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_BasicFootIK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_BasicFootIK.uasset new file mode 100644 index 0000000..4d3a1f4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_BasicFootIK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63c0417f3b6045558911f27b41c79baa7338a2ee96d54bb67eee399ed8c89710 +size 590741 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Body.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Body.uasset new file mode 100644 index 0000000..b3fd00c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Body.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5292dd182fba746bb222ee8ca0bad62684478259185ec288325309b28617b95d +size 12653677 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Procedural.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Procedural.uasset new file mode 100644 index 0000000..e12ec6d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/CR_Mannequin_Procedural.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d85bba6546413f9e1b1cab62a290afcbd4e478c9e47ab02cd3e0578de1a8403 +size 2336132 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/IK_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/IK_Mannequin.uasset new file mode 100644 index 0000000..0411e13 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/IK_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66e8025f539146e688c3cb69af621514f295765cf4e0d30312ec8bcaf93274b1 +size 143511 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/PA_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/PA_Mannequin.uasset new file mode 100644 index 0000000..9e57443 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/PA_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32d8ce58623c077e25c0f260cc549377d4df1e81662e07bb96cc48113b78ae6d +size 289317 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_anim.uasset new file mode 100644 index 0000000..c59e631 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ce02adaae5946a30bfc0f348815218640229bc92e34561509b14a84fbc38cb0 +size 105206 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_pose.uasset new file mode 100644 index 0000000..65ee716 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:217815a8fbb02209d8ab7a27815067e8eec7c1c31544215cb9314863ee323166 +size 207531 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_anim.uasset new file mode 100644 index 0000000..4f45d23 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c95783ce9cfb7ca8bfd429e2c9007bf01b266c2ffe6fbb23cdf8741b7dbe1c53 +size 105216 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_pose.uasset new file mode 100644 index 0000000..72fb9e6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_calf_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a96b8c6935bf170cdef21b026148f07ecacfa3af22651f9b463fbdb8fc52e6df +size 207531 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_anim.uasset new file mode 100644 index 0000000..97f005f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a04dd3997ef95806234d2df5d287aa41f5715d1eae056de5a7e037e2f1aa803d +size 105242 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_pose.uasset new file mode 100644 index 0000000..05a78aa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb547ba1ee69dd4171187abb5dbc0f79d10844049ac78d881b6cb00aadd46cb2 +size 205837 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_anim.uasset new file mode 100644 index 0000000..e9853af --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4707a92febba42851f293c526a373843fa181ab0d9071601c5e823e18b126952 +size 105244 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_pose.uasset new file mode 100644 index 0000000..bce7832 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_clavicle_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efcbfde96d0c561d4f2737354d992561d6eed0855468dd77977275ccaeb22b59 +size 206439 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_anim.uasset new file mode 100644 index 0000000..a6f1e9d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21cafd1a5ee408867a834fa06245f6d074d98e180f78edd1b9affd69b407277f +size 93214 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_pose.uasset new file mode 100644 index 0000000..9581023 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcad5bad764e842adfc6088f1c054fd1db3eee86236ce421f345e2cda31b9210 +size 132277 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_anim.uasset new file mode 100644 index 0000000..489b0b4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34368aa5b3857b5d827d8b1706f4432e834823afa6159a0c4c4ec126dfa28d60 +size 93216 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_pose.uasset new file mode 100644 index 0000000..84dfebf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_foot_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac5b5b1f6aa71eef22067ec547533c8d45de8d65f0808ecb59bddbc1cd92dc8a +size 132277 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_anim.uasset new file mode 100644 index 0000000..5a82929 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ff41b8e57ca295e00365dc7a1c6ebc364f376d7cdde9ae0378badda8b30f3f1 +size 99214 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_pose.uasset new file mode 100644 index 0000000..8ca1d0a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c31dafc62605422678e3d2b2401ce8db9f41689d292184d96d5a164a492f8d16 +size 169307 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_anim.uasset new file mode 100644 index 0000000..7af6ceb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52c13d7f7a1b328aec3234a8ffa386b12c14069fe7217446c9c2080fb7c89b10 +size 99216 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_pose.uasset new file mode 100644 index 0000000..fee8a06 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_hand_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75052755245be0feb091c8834f6b611c75d6780cec4bb525c91d42e7be51b5a4 +size 169307 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_anim.uasset new file mode 100644 index 0000000..b7281fb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b23f1f90d33078cb5c0e1cb0b2f066491daa7a95cc4c19e0ba3e2e7b1ec6db6 +size 129596 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_pose.uasset new file mode 100644 index 0000000..1661689 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8af73466c4cfcf6fb805ac23e7e72b6b06297d592c4445a77ef72e8dbf057d02 +size 361749 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_anim.uasset new file mode 100644 index 0000000..dc5af61 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84ac4e43287f2167d1d10a786eb3b09a7ae5178db58b370617e38fcfe384173d +size 129244 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_pose.uasset new file mode 100644 index 0000000..d2083f2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_lowerarm_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c371e1e54db220b98992146383a757748eda80701ce9e743048a7209385666d +size 361749 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_anim.uasset new file mode 100644 index 0000000..c249f39 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:626f16b8aa0b278034ad93354f36f340c5eedb8dc0f9e1eb2d2ec6840e0ae1f0 +size 159223 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_pose.uasset new file mode 100644 index 0000000..e85bf2e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a500c9e6709807db106a20a4ce9a1b6f3197c5ae5e493535f7424908f6368454 +size 558074 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_anim.uasset new file mode 100644 index 0000000..0f01e9e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbce93d680b6e54608205212e7cf59eccbe21f129ff8bbf5d6cfcbe56478b900 +size 159225 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_pose.uasset new file mode 100644 index 0000000..00e6374 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_thigh_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:102b63287bd6c6c8a6bda114c9cef7bed39086c34438a8fe84cbfc0270f6a5f0 +size 558074 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_anim.uasset new file mode 100644 index 0000000..efc5d1f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a54532246126cd1d7207cffc9f8ce45de791c7eba4aab45e7905dd526ac7717e +size 171244 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_pose.uasset new file mode 100644 index 0000000..4e66f77 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34094764ce886228c6093d2f583f4fafa22aaa3b387c9b811d36da8602573460 +size 641738 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_anim.uasset new file mode 100644 index 0000000..dee9f14 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90c6fcad7188756cdda1fe49a1ce75b698ecfef27c8b88f0d2ff3527adf0dcbc +size 171246 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_pose.uasset new file mode 100644 index 0000000..2ea225c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Manny/Manny_upperarm_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc2fca5f70a43c8c98ac21acd711b2436de7d7e42cf321b30450a20686dcf020 +size 630300 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_anim.uasset new file mode 100644 index 0000000..0519f06 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b540e31e20573fb60b5293bc5850e26d933f791c04695133ab4f902670725679 +size 153784 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_pose.uasset new file mode 100644 index 0000000..46bf9c5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a30d62bd32f58e232b30519721808fd49070f67e3ecbe101520661a6dde7e5e4 +size 245262 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_anim.uasset new file mode 100644 index 0000000..e394a3b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f91f63f68351873f8ef00dba1ef9aef6c334c90454da4460e9455332fb715389 +size 153775 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_pose.uasset new file mode 100644 index 0000000..32532f4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_calf_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aaa44d06f750c8ea5435bc42f2387ff650dd2453137f2fcdffe356228220611 +size 245343 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_anim.uasset new file mode 100644 index 0000000..83eaf73 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd4c1f05ea456162e35c61d31b0c9c5034531c4940fa857d30f93889b18ab1d5 +size 147743 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_pose.uasset new file mode 100644 index 0000000..39c4e5b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76e3889df4ee68657936ddbb13d8db6de9f8b4818128d4882fb292f1a577566f +size 206014 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_anim.uasset new file mode 100644 index 0000000..710f59a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f79419b8a761026ffaae77fd07703404c92681fec9df9ec45819bcb9a9794f7b +size 147492 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_pose.uasset new file mode 100644 index 0000000..b05efff --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_clavicle_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:277f4410f9b80780972698064e0f4cf192b4375610506efa5d510ae0e8404f1a +size 206115 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_anim.uasset new file mode 100644 index 0000000..c2b2a80 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8208edc319e609d4e6b1d326e62a877063d79cf503158a4bc1878fd02a93ed58 +size 135462 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_pose.uasset new file mode 100644 index 0000000..909c41a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6371fa829ebf962697f5be5b5650ceb195803537302f0ae3d1a0cd4111d74584 +size 132254 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_anim.uasset new file mode 100644 index 0000000..558cac0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a6c53687aef19e408c06f41ee035835ca209af4ad8ff679152c47d0daf29600 +size 135464 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_pose.uasset new file mode 100644 index 0000000..d131e90 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_foot_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d83f07adb29976a36ddf99f245f24b5d3868ad3fdd5f0025acc9231346a014bc +size 132254 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_anim.uasset new file mode 100644 index 0000000..f4b66e0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed9e80e359140fb44037acbb066826198c27233bdd7c253cfb391ed6dc30d07b +size 141462 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_pose.uasset new file mode 100644 index 0000000..ace6e4b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b1aaee1c184ab23f77d462372726b9be294b25dc77eccb2f444e54ce72be812 +size 169284 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_anim.uasset new file mode 100644 index 0000000..a414ba1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83e66651a64a65e141384f67f3f036d82647c1c3024437b0b0ee0cc59533f6db +size 122362 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_pose.uasset new file mode 100644 index 0000000..dc73bac --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_hand_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac2d942e4fc64538231e1c921ec9d008a6d1bdecf76dc130bd372c687f781c08 +size 169284 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_anim.uasset new file mode 100644 index 0000000..1f49344 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9976a76b917eac5bdc0e4296a91d9816a198b8548d18594c3f623826726fe496 +size 158389 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_pose.uasset new file mode 100644 index 0000000..2c8178d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba6822ed243f8452e0392cf4f198e636de9c868053e2261d63d8292ef96319c7 +size 399664 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_anim.uasset new file mode 100644 index 0000000..e2e3009 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:892f16dfdfa0d0d9957d427c7d620867adc3a33f3e7814fd92f91c7d696bf027 +size 158391 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_pose.uasset new file mode 100644 index 0000000..f933f99 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_lowerarm_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dee51a08a93109dec908d4cdffeba2be2ccc48d84cbed47048f99b12f43e7ddc +size 399664 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_anim.uasset new file mode 100644 index 0000000..5566b31 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:381b95e60bfc26a64d8595e047ea6a007e66adeeca20331d23127a82cdda783b +size 182369 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_pose.uasset new file mode 100644 index 0000000..90ff7c6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbcbe04fcc189470ee8567f1e4363ebdd6b978cd018af41df6d6e849335dbaea +size 557750 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_anim.uasset new file mode 100644 index 0000000..5c41404 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c4a3386b121b0da667ca82a62986fb54575533bcaaf8797ee2ffb49e4b2629 +size 182371 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_pose.uasset new file mode 100644 index 0000000..c7cc3ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_thigh_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4b3f86720ae78ec16ebcd7dcebef72bb89ad5216dc524cd193b6396faddf6a +size 557750 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_anim.uasset new file mode 100644 index 0000000..1e3518a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cedcccc8e20190a00ff514c5467ed6781748deb2178f856221f833b99c89f1ac +size 170390 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_pose.uasset new file mode 100644 index 0000000..15ec31f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_l_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:910546553703424dadce708ffb02ba320b60abff1b4bffa7ba637a1f76d8b5a3 +size 485575 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_anim.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_anim.uasset new file mode 100644 index 0000000..28b6e76 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_anim.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02a0a2a388445683c68518c0062f7aff5baa45d8b490b8a0b7a0571a5977751a +size 170392 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_pose.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_pose.uasset new file mode 100644 index 0000000..886ec5e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/Poses/Quinn/Quinn_upperarm_r_pose.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:167ccaa713eaa481437b4128bab282dd8e46a56c403d925f7a95ec8706289b70 +size 488585 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/RTG_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/RTG_Mannequin.uasset new file mode 100644 index 0000000..0f1e5b1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Rigs/RTG_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7f55f2e0aacf145904d8cbd2459af8739f043c928fbdc0b48bc8b92efda0fba +size 23761 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset new file mode 100644 index 0000000..1b2b3ca --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adcfc2e6fb97e6625a6b358c6a2d4a96ac7f3cf7dea0551c486344291efa8295 +size 3612711 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_BN.uasset new file mode 100644 index 0000000..2ee75cc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32a5d96fb2745b0d2c2a96aa6825a64ea177c9a1af64cbe7156a5a80852e60be +size 18514263 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..daabd36 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6571dd4986df8ac46bbbba1f736ada982706ab0e5502a161142e368f4d7f52d +size 10272081 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_D.uasset new file mode 100644 index 0000000..8b78bd3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fbb07cb01e9eb29f8ceabbae27ede593bd4ede98957f492f197208e8db5dab2 +size 5740449 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_MSR_MSK.uasset new file mode 100644 index 0000000..88f75ce --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1751ca6b12e656c02c5e0fd50e1856d731c0304b81a6740a9847b2de30021547 +size 11038284 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_N.uasset new file mode 100644 index 0000000..9aeb54f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a51fe0604dcb201b32ba900006f565b0330d826d3f6f891fb7e884c3cc7235c9 +size 7198345 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_Tan.uasset new file mode 100644 index 0000000..e016ef0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_01_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bdc44109c7e4891fc8c8b15a98d06300f2c4e2b55f5513fb15a380de31a28d1 +size 1357866 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset new file mode 100644 index 0000000..1824c76 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb0e8f5e63df11c269dbffaf6a8a9c9ccf00261e985dfd6a42499ec78ce816e3 +size 8341487 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_BN.uasset new file mode 100644 index 0000000..7573bb3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6cc567ea332550b67761d3c8afface743880d9ca2d7d701a328de16b4c9a0c1 +size 21139622 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..e3a6b16 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acceca89b72d45be61b978671e8634e40b3439da0565565424bbb75bc491771a +size 13144152 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_D.uasset new file mode 100644 index 0000000..cb90248 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f7e39f621fd09895097c8ef9be45b4c9114ff5434d41ff4875640377d90c048 +size 9033617 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_MSR_MSK.uasset new file mode 100644 index 0000000..a0cc5b4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cc57bbf89333f7053772d9d3ce220d5185af3b7b98e2b1be3710736ed81e385 +size 13677710 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_N.uasset new file mode 100644 index 0000000..02a6a68 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:774b5f12e83544218ad818e58a00e0651627e2a2139c6ae81740c32188b88790 +size 7269429 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_Tan.uasset new file mode 100644 index 0000000..1c513fc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Manny/T_Manny_02_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb1d7c621fdcf8b600bcfb7ac7f10f5e1b1c9016a73997f4e024e94535a0e073 +size 2153886 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_BN.uasset new file mode 100644 index 0000000..4fa1e57 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e399a20c77dcf93205859660f6fc938fa18c7eb0a7eef84fe11be57f44333f8f +size 16108696 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_D.uasset new file mode 100644 index 0000000..7e730a1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba3a9f3c53be7bcf473543fd7db586ddb25aeaa3654f2b334a42d97403e4cb4e +size 4711231 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_MSR_MSK.uasset new file mode 100644 index 0000000..bf17e43 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fe6e4d0e9dfe6b37fcfff10fd7828e57e303552d58d5097032c8d88e3f5ef01 +size 11656243 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_N.uasset new file mode 100644 index 0000000..dbe9183 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09b0a749d38658217ee32ff0d7cf875b8360eae15d10b2771a66edd8f49568dc +size 5217772 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_Tan.uasset new file mode 100644 index 0000000..abb1683 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01ID_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a304782fd7992f18692fc5dc9b6fd121ec15d8e4088c09496cbfa73e5ebb466 +size 1104725 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_ASAOMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_ASAOMASK_MSK.uasset new file mode 100644 index 0000000..a658a84 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_ASAOMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1d294824a3393a5b7abbef09dbbd08cea1eb248ca935b6727792a624d839f51 +size 5834939 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..2ca4e1b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_01_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7a0c808b5e0ea524c53e9be00e25bfa2bd2261b4f0ddfde14f3823e6c34f7ae +size 12400035 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_BN.uasset new file mode 100644 index 0000000..5b55852 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae64881694d7d129425271e9749ae310de06e4f6b44b431048263a0bdd79269b +size 19706980 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_D.uasset new file mode 100644 index 0000000..9037825 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff3848b1eba0eea285b8ed3f0bf32b3d06fcab26877eb30f56e55a1fbadf8f24 +size 6732599 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_MSR_MSK.uasset new file mode 100644 index 0000000..2ad72e2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f07854549aed14b0a08a810536fd3f92c7004f8cf325b6c229e199b77a70d210 +size 13169873 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_N.uasset new file mode 100644 index 0000000..610e4bd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:291193c31c0afedc313659a9712f91d771c5721af47dd1a074be93d6c97bf2a0 +size 5217772 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_Tan.uasset new file mode 100644 index 0000000..a82b78a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02ID_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52e073a5286c21223e535aaa14196064e32da69a412bafaff889e45f1100f2cc +size 1758592 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_ASAOMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_ASAOMASK_MSK.uasset new file mode 100644 index 0000000..45c6ee6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_ASAOMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a8a3b6407fdeb750079ccfbd392598eb7ed13de5eebe3b3e02c4f1905021601 +size 6902078 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..4fdb67a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Quinn/T_Quinn_02_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6a1d60f0eabcd7d2655fb7619c7141f44ac7f2670f9f4113de67c5943c6c0db +size 13427967 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Shared/T_UE_Logo_M.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Shared/T_UE_Logo_M.uasset new file mode 100644 index 0000000..4bc8f96 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Mannequins/Textures/Shared/T_UE_Logo_M.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47dae3eb7ff30f4c72bec58a775ee73523014520406e32c110a28f0ab1e092a2 +size 70054 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MF_OccludedPixels.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MF_OccludedPixels.uasset new file mode 100644 index 0000000..6ab6003 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MF_OccludedPixels.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54a0408daab81a30343ab5a6eebbb7341858fd4327eea5a98bedd3703386fa26 +size 101110 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_ChaperoneOutline.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_ChaperoneOutline.uasset new file mode 100644 index 0000000..442c544 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_ChaperoneOutline.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f16e14b4ebea466a0b9df780a72ba103a1c6673f7388c1dd2baa13dc18c70a47 +size 83176 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_SmallCubes.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_SmallCubes.uasset new file mode 100644 index 0000000..3063540 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_SmallCubes.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62bcc58df01d0360bc1a9f0f15ca64e9c190b8bb68e1d577c4cf1e437ad8509a +size 93815 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_TeleportCylinderPreview.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_TeleportCylinderPreview.uasset new file mode 100644 index 0000000..dc1b7b5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/MI_TeleportCylinderPreview.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9dc5a4c5036697c43ca19e908b333a5957af0f3fdcb519ab55afd01a51c97fe +size 86595 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_ArcEndpoint.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_ArcEndpoint.uasset new file mode 100644 index 0000000..45bd2e7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_ArcEndpoint.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df74d0c1f039f45bf279012ec85336ff35ac6615c0b11b4fdebdcb8d675ab1b9 +size 39968 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_BaseMaterial.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_BaseMaterial.uasset new file mode 100644 index 0000000..cf9c7bf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_BaseMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bba49da44c24411de48a207a27be18cd99c687e3c3bd5bf3a54ef2acdb6f74f +size 84767 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_SplineArcMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_SplineArcMat.uasset new file mode 100644 index 0000000..6d84d80 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_SplineArcMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ed3c39989e69ab1d5b009cbbcdb162a079fed671c93dccd8e1576d8d96fbb7b +size 96939 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_TeleportPreviews.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_TeleportPreviews.uasset new file mode 100644 index 0000000..644ddf6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/M_TeleportPreviews.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73a23e29e515999882607de2e1893ebd9049959d58e00b0b30f0fa3965383140 +size 98377 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/TeleportMCP.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/TeleportMCP.uasset new file mode 100644 index 0000000..7bc999d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Materials/TeleportMCP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e64974c83716a0270f5502fd90bd9a25a9228d9a8681f3fc94f0a8d8e929524 +size 1778 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/1x1_cube.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/1x1_cube.uasset new file mode 100644 index 0000000..f7bb0c7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/1x1_cube.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93348fe619da2f377eb7a1f9abb2083c15a15004d032f433ad6b7d04fd4cbd58 +size 66048 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeaconDirection.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeaconDirection.uasset new file mode 100644 index 0000000..2e61cec --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeaconDirection.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e8320ab60f49afe35ae68ea9ec1c0f086751c9b1071360234d2d1c1e874b61f +size 23071 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeamMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeamMesh.uasset new file mode 100644 index 0000000..6a91c9f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/BeamMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fec217aa37eb7a505128347b5af8a3ccbf1c5337b18d0b10d2b45d89e15f443 +size 25004 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/SM_FatCylinder.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/SM_FatCylinder.uasset new file mode 100644 index 0000000..e3a33aa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/Meshes/SM_FatCylinder.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:856efe12362b5c9d2c2dc382bc1288200833381f07da2392409357d619d682c3 +size 23281 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/MovementMode.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/MovementMode.uasset new file mode 100644 index 0000000..556cfec --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/MovementMode.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01c270fa3a06b43ac3906b8373466bbf5c0fcaa6a1efe1679afecbb55f79a804 +size 5307 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/NewForceFeedbackEffect2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/NewForceFeedbackEffect2.uasset new file mode 100644 index 0000000..656953c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/NewForceFeedbackEffect2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9e956a394bfe6a02e1101caee08e9f82779ef95bba5dd861e96a7b4f0c9bba0 +size 2482 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/VOIPAtten.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/VOIPAtten.uasset new file mode 100644 index 0000000..ee5efbf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Character/VOIPAtten.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30b8f0721f0f7186cf161fbebeb3f633f53b36cd80fa756b5fa902c838de2d94 +size 1368 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/CustomLocalPlayer.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/CustomLocalPlayer.uasset new file mode 100644 index 0000000..2ec26f4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/CustomLocalPlayer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d1dd04c7829b6a1f8c3495acb74dfd3f1ddb8a89c830f549c55e9bd58acd674 +size 6206 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/DefaultGameplayTags.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/DefaultGameplayTags.uasset new file mode 100644 index 0000000..0895ccd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/DefaultGameplayTags.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ae95b01c3b9586c8865a8608f5a283501868837b6f2ed8b4449bf68a05c8355 +size 4870 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/Gestures.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/Gestures.uasset new file mode 100644 index 0000000..71ae424 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/Gestures.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24137f097a0cf0e3cc12bee282c3e692e035efc8df1bf17e1657f6e6278ee599 +size 2821 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBP.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBP.uasset new file mode 100644 index 0000000..0ac6882 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30f6cf32bcdb82d86b3a1c4279764c7010d29a1e0110a0a9cee61eed816489f5 +size 592475 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBPManny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBPManny.uasset new file mode 100644 index 0000000..b672e63 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspAnimBPManny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:113b070c95cabd0083641039ba8b9eea8513e343fc4243dd9f30ed12d14664e4 +size 604888 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlend.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlend.uasset new file mode 100644 index 0000000..65ee1f5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlend.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6a701a4008fcfde07775abfb54f0df35b4318316afd2f9eb8a2a6b6777d41af +size 7885 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlendManny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlendManny.uasset new file mode 100644 index 0000000..4792a35 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingBlendManny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:888b3957bbb2c7423619b921b6de9c9b3f1ebed7eb9250967a16ac2179a5d70e +size 8868 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHand.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHand.uasset new file mode 100644 index 0000000..e7924ea --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHand.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dfebe27b025e487366129436fbc9a2fd1e10b66b31746fbe3d6d3a05b4daf98 +size 2399836 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHandManny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHandManny.uasset new file mode 100644 index 0000000..322988f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingHandManny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54effd14dde85d288320028e5f51ae07183ece445c8da502765046669bbab9f3 +size 2380291 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysics.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysics.uasset new file mode 100644 index 0000000..b030e54 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysics.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c749c5c6ed2962234b07a3f5e01febce333d125eead42610649556c8a69692f8 +size 126272 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysicsManny.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysicsManny.uasset new file mode 100644 index 0000000..3622fd8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/GraspingPhysicsManny.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:058754dc3612df66d55038a7ff765967ce894f7b85f06376a20a6f20ce86ef4c +size 69933 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/HandAnimState.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/HandAnimState.uasset new file mode 100644 index 0000000..c399504 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/HandAnimState.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b19e695b3faa89d8093b9b0b5eef960298d0b71d340459d5411e9ed0384b1d9f +size 3023 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/PhysicsTossManager.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/PhysicsTossManager.uasset new file mode 100644 index 0000000..004c10c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/PhysicsTossManager.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11d92c3fdb9ad0f88ccc631bcf239dff5a6341f79711b3eca311c6962c3e5292 +size 285951 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/TriggerIndexs.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/TriggerIndexs.uasset new file mode 100644 index 0000000..4f65169 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/TriggerIndexs.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f230faef606a51133b4b90f52b6a3faeb10bea4e451bb5f15a27637d05880aea +size 5143 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Grasp_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Grasp_Right.uasset new file mode 100644 index 0000000..470b2db --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Grasp_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c265c4a5179cbc49b00368235635ed1d8335dfca016bc78ff8e4948a095beb6 +size 29844 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Idle_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Idle_Right.uasset new file mode 100644 index 0000000..8c0b03c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Idle_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b8e07d4a99b3dc2ced7b8d4f05d986f6fdf2797eafcb50d525ae89c4935c9df +size 47127 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_IndexCurl_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_IndexCurl_Right.uasset new file mode 100644 index 0000000..d4b7472 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_IndexCurl_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04e0970c4cbc59c044ccf98219b746a974d8c71572d4ebcf35c894fd01d09264 +size 30163 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Point_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Point_Right.uasset new file mode 100644 index 0000000..137e9ad --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_Point_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:136de5f21df470e3322645d54b10ce70ddb1fbf24fe1758964caa61fb188223b +size 30075 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_ThumbUp_Right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_ThumbUp_Right.uasset new file mode 100644 index 0000000..5ef3f67 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/A_MannequinsXR_ThumbUp_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:170953d0519e3e7b9230a1e4347136fc1f102786f6cdeb6f73ca1b858f61ac8a +size 30136 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/GrabAnimation.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/GrabAnimation.uasset new file mode 100644 index 0000000..7b5b519 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/GrabAnimation.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d60663acdcf836b1f18af8160e55605eed835f7860998f77bf8c5c64b935e7 +size 18098 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/MDT_MannequinsXR.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/MDT_MannequinsXR.uasset new file mode 100644 index 0000000..c1c81d8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Animations/MDT_MannequinsXR.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12e935fb2b9c993c1fea32f460877f99625f5ac9dc66e45713cafbf494c69c1c +size 12698 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/B_MannequinsXR.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/B_MannequinsXR.uasset new file mode 100644 index 0000000..a8aff4c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/B_MannequinsXR.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c018cfaa312a441aeb0c3e3adfdce495c1da70d99effb180b75d8c6ab36b58cf +size 13722 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/CA_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/CA_Mannequin.uasset new file mode 100644 index 0000000..98e7dff --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/CA_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b30315c5268c4c6f2bf734d3b92e2775cd157d159f4bbc5caf9caf8e6fa107d +size 4986 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ChromaticCurve.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ChromaticCurve.uasset new file mode 100644 index 0000000..7bd3825 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ChromaticCurve.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92f3173629d4560f25529f361141d58ac6fa21a8d6191c537f703b13ee929b73 +size 6447 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_Diffraction.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_Diffraction.uasset new file mode 100644 index 0000000..c815496 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_Diffraction.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0926a93a1f8fc963c4f1ea8a3938e268aac97d8637602351aa2a24214f76889 +size 32083 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_logo3layers.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_logo3layers.uasset new file mode 100644 index 0000000..ef5ad5d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/MF_logo3layers.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec0f6fc2eedc7fccfcf37ea6ea4179896bc99cb556dcd05a1b467f1a0ed64e4f +size 54856 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ML_BaseColorFallOff.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ML_BaseColorFallOff.uasset new file mode 100644 index 0000000..e1f8e51 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Functions/ML_BaseColorFallOff.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd8135c90a2ee5475a026fda4d521aa5587c7cb37ecff60b5918ffbcbee74ad1 +size 13751 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_01.uasset new file mode 100644 index 0000000..8a6b70a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5ababd4023308c31b036cbcb7807007169945e2be7811b495edc82405933917 +size 15549 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_02.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_02.uasset new file mode 100644 index 0000000..2dcd1f5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/Instances/Manny/MI_Manny_02.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc3290f2fdab2392cdfe63ca1f39963eb6a3ab53e94c614b2b227cbebad8fd7b +size 20126 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/M_Mannequin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/M_Mannequin.uasset new file mode 100644 index 0000000..4dbd090 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Materials/M_Mannequin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d0de5cf3c470c1ea90877e3518ff9dc4b96545b56f0b0e8045519907c7bfeaa +size 79412 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/ABP_MannequinsXR.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/ABP_MannequinsXR.uasset new file mode 100644 index 0000000..20db8ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/ABP_MannequinsXR.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01de832d0c6c993b0f4a9d01605080af5cc8e11c625003c814e63a4e5955f999 +size 226849 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_left.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_left.uasset new file mode 100644 index 0000000..37ff094 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_left.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49761abd36fc336199f308b808a05edf93c531bc7e5de50e0a1a7277ebed5759 +size 2341113 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_right.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_right.uasset new file mode 100644 index 0000000..d2c4d9d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SKM_MannyXR_right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a900ce60070e4f6a8286c3a25de9e2f55dc50863fbc89e95824c07d97f2a9973 +size 2340333 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SK_MannequinsXR.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SK_MannequinsXR.uasset new file mode 100644 index 0000000..778c06b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Meshes/SK_MannequinsXR.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa6834d20b534c4919ed5d11564c65f3a8dc46462b12ec1e63ebdf019a46f7fc +size 53130 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset new file mode 100644 index 0000000..89c075f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_ASAOPMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce32d32ba29bc7f506de3cecd6fca0f2f5890530ca3df2ad4028aacd5b3848ed +size 3612723 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_BN.uasset new file mode 100644 index 0000000..23a94da --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cab472105172ff531e7d4b581d49a0b0267c3a01192a9b346d167227a01cafb +size 18514275 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..f235d70 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:663d2b0ae07554ec74f1476f758828be6723bb4539974f02b9e06b445e25de82 +size 10272093 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_D.uasset new file mode 100644 index 0000000..1267d9e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d3d691c0808c6378042935fd497fa6328fe6a0f65fc14ede6ef74bd897896fd +size 5740461 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_MSR_MSK.uasset new file mode 100644 index 0000000..6209c01 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:594997242a9e782913f417bf7b66bfdd4e973f61d37f19517946b00f8330fdcd +size 11038296 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_N.uasset new file mode 100644 index 0000000..da667a0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d855986290da65aeb62f194871eccb3345b756a16392b74f08e484de1187cb1 +size 7198357 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_Tan.uasset new file mode 100644 index 0000000..cc74e2e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_01_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5198cf31123bfc90086843c08469c7446089a9c02e4730ac5a237770b4bcc38 +size 1357878 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset new file mode 100644 index 0000000..815b6d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_ASAOPMASK_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:799d88e7e15925be4b7ebc9bd36a0e4a757cdf46e0cb2c7d3d09ebc9259c0f53 +size 8341499 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_BN.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_BN.uasset new file mode 100644 index 0000000..8bea4d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_BN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec720ed86dd6f43709a1da7edeb4a84adcefc17ee1417fbe58577ebf61adb580 +size 21139634 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset new file mode 100644 index 0000000..6b381a5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_CCRCCPlastic_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cbed690c700c91f50e0869f80afc58d5cd23827937560acfc509840ea9567a4 +size 13144001 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_D.uasset new file mode 100644 index 0000000..752f2ce --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c352d07a48e228219b32dee09b7668594655cfca59d97b15dc8cb1a49e1eadd9 +size 9033629 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_MSR_MSK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_MSR_MSK.uasset new file mode 100644 index 0000000..dfd2fda --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_MSR_MSK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec66618f04ba83fca769b035feb5c006ff10a3e96e661484bb1dcd6b5d810dac +size 13677722 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_N.uasset new file mode 100644 index 0000000..c5c838b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8966f6bab5c41e69e5145b4f17e95ab65fb31e917c74eef271051a566da80c7c +size 7269441 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_Tan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_Tan.uasset new file mode 100644 index 0000000..558b417 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Manny/T_Manny_02_Tan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38467e22cf6c7828771d7f8184c9d6fed06b6711b35614f9760b64403e3c4945 +size 2153898 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Shared/T_UE_Logo_M.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Shared/T_UE_Logo_M.uasset new file mode 100644 index 0000000..906fae9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/GraspingHands/VRHandMeshes/Textures/Shared/T_UE_Logo_M.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f07764fd1a0041865c1cf996725714a2ba3baf0df51a24e58f0adebdd99d5d8f +size 70066 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketBase.uasset new file mode 100644 index 0000000..3143dac --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9afbf8d9f9e4bbd7cb6db658f61a41bffe5c58c0cda80b9bf40bce72b13c216 +size 16993 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketMat.uasset new file mode 100644 index 0000000..6f7bc67 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/HandSockets/HandSocketMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60d11e9fd42cd8cf37367a275aa7c98a8b428cc98942013db797bca364e2e287 +size 114250 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PendingPC.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PendingPC.uasset new file mode 100644 index 0000000..008bcb0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PendingPC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b2167e82facf92adbeb93c08a63b213ea320776aa96d89e31d0bc35642710b2 +size 27086 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Alum.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Alum.uasset new file mode 100644 index 0000000..99a6a72 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Alum.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a021ff228a4ae98acdcfcca3b75825059bd652798dc000eb5e76f5dc8f4e798a +size 1379 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Hardwood.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Hardwood.uasset new file mode 100644 index 0000000..05822fc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Hardwood.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99d0b46af625ca3c6feba9a8fe1fcce90e7e3fd972c2389cb4b82deb4384ab88 +size 1477 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Steel.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Steel.uasset new file mode 100644 index 0000000..9111aaa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/PhysicalMats/PM_Steel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b359ecff9c2e3953ce83e8066ccf15a5ceb63f8ab5346bd15039eb6896b0b19b +size 1383 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Template_VR_Player_Controller.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Template_VR_Player_Controller.uasset new file mode 100644 index 0000000..db7358e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/Template_VR_Player_Controller.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c8d0e7108076c15c4f87b88ea83abd84b50226f91a2c83c7007f867365aeec1 +size 238162 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameInstance.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameInstance.uasset new file mode 100644 index 0000000..553aa52 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameInstance.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71935ffebc175ba938413214c8fc35a1401603b164ee247025ecaf5dd9c44e4a +size 9094 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameMode.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameMode.uasset new file mode 100644 index 0000000..ed10686 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRGameMode.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff2a09974e04b0cb5e29b828479eab8d9eeb50a464db264533519340783ebcf1 +size 26008 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRViewportClass.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRViewportClass.uasset new file mode 100644 index 0000000..aa56d16 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Core/VRViewportClass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff16568830426dbf60838ea138a16bfe440db8055e1ed3f15943229c1816c07 +size 6006 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush13_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush13_StaticMesh.uasset new file mode 100644 index 0000000..5910f8c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush13_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10391fea96ab434d99d1d23737016a268a2c42440c7fad7d07f12c446d0f13a0 +size 18467 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush14_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush14_StaticMesh.uasset new file mode 100644 index 0000000..cbfebc2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush14_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4e4a7867f411e848f08c9170a12920a9500666027432e8ef6c233a7dbf41c55 +size 24257 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush22_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush22_StaticMesh.uasset new file mode 100644 index 0000000..0896e49 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush22_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebd328ddb48f91e11e4573b8357152ce511bdd249a2cdd8c629a770026651045 +size 17077 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush2_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush2_StaticMesh.uasset new file mode 100644 index 0000000..ce850f7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush2_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8250f324987ba218861508e39eec873fde808e69e570e10547bad10df042a0c +size 15733 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush3_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush3_StaticMesh.uasset new file mode 100644 index 0000000..6e3dd00 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush3_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4653a80131ccccae3c410b5ac3c21b6e8575ef19b950e1fed0226a57aba5cd +size 15450 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush4_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush4_StaticMesh.uasset new file mode 100644 index 0000000..3f23cd5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush4_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2bc60803baf2e8b20485b5b0ec2facdc2384794f93c66754b2cabc5d64cfcf4 +size 15912 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush6_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush6_StaticMesh.uasset new file mode 100644 index 0000000..828d009 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush6_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e90fe931bea94598a40a5f072e81f0c40c05ae18375267915c5dffda2af0570 +size 16340 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush7_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush7_StaticMesh.uasset new file mode 100644 index 0000000..2747418 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush7_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91a7bde3205120e3ce15cea8b3ade316a2c3d6db86f7f3833fb7ba4834f15529 +size 15585 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush8_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush8_StaticMesh.uasset new file mode 100644 index 0000000..ca5f886 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush8_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6f64c3beeac932365b4a9e55db67408a28afd156c5a4a24ff107881c7304f4b +size 16785 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush_StaticMesh.uasset new file mode 100644 index 0000000..6c62f89 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Box_Brush_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fc221df0d911f5870dd3b1c23d01399a86b89612b69be58ec3fc9f65220a19f +size 20563 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Curved_Stair_Brush_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Curved_Stair_Brush_StaticMesh.uasset new file mode 100644 index 0000000..7353f24 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/LevelSMs/Curved_Stair_Brush_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66b277c3d6d61654ce7d50678eeb8eb3be4d3f7d70e88b141197024405606a7f +size 22965 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/MotionControllerMap.umap b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/MotionControllerMap.umap new file mode 100644 index 0000000..c65a3ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/ExampleMap/MotionControllerMap.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d015c184eb3a1c25f797831cf80c2709992944e896cfe693d981f3766235714 +size 785879 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/BP_PickupCube.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/BP_PickupCube.uasset new file mode 100644 index 0000000..b6f7678 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/BP_PickupCube.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd1a07bf541a81e9cce6b581bf7cdadb1020431e5e3c32c8d456b15f392e8e92 +size 150174 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonActor.uasset new file mode 100644 index 0000000..464ec13 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9dfb7afff84b77ce2fb54b930025a7c10995cf582ddda533a578458ece2cf75 +size 101811 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonBase.uasset new file mode 100644 index 0000000..526d831 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64cc2164c96336613f8fe61a818946638eeb7b0bb21402e1fd63ed6fb61d1648 +size 35104 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonComponent.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonComponent.uasset new file mode 100644 index 0000000..894caf2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonComponent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95bff05325a75be2a611e3470e106230ac6cb2bacc602f184ac6a88ea5e5b11b +size 379820 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonTop.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonTop.uasset new file mode 100644 index 0000000..c1a6218 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ButtonTop.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a81a6225e3093b7eb97a204fcdf6500ad2b9c2dea96dea3f55c576a8bc59986d +size 66155 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Button_Mat_Inst.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Button_Mat_Inst.uasset new file mode 100644 index 0000000..e2ed19e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Button_Mat_Inst.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80150b19456873f2b10d0e4b9b989640bc09f71baeead650a50f3beb306d875b +size 66333 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ColorChangingButtonComponent.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ColorChangingButtonComponent.uasset new file mode 100644 index 0000000..ef0a800 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ColorChangingButtonComponent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69830f818201f1723d8983d309044fd0f41227fc256509527e072f3747012e14 +size 62983 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/EButtonType.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/EButtonType.uasset new file mode 100644 index 0000000..cdb8760 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/EButtonType.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fb8ec1bd634dd924cf1c44fd8ee5e0aa39229e875137f63d6181938be77cef5 +size 2304 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ElectricalObject.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ElectricalObject.uasset new file mode 100644 index 0000000..4159efb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/ElectricalObject.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf4cc3e58aec9a98fa0bfe9f03ada09ce59d8d711734f053d9996c8b93bbd13d +size 12597 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Gray.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Gray.uasset new file mode 100644 index 0000000..4967146 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Gray.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a23deb87e91daf4031a23e2c72bc6c55c61dbf997b498c969f1b735d74dabea9 +size 6069 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Green.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Green.uasset new file mode 100644 index 0000000..8a8d315 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Button/Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61cd829c1307abb54d7865e377f7ca8f11eb73496809713d1695055985d14a6b +size 17999 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Cabinet.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Cabinet.uasset new file mode 100644 index 0000000..e1c5ebb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Cabinet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23dfe23b8227529cf453cd66af662b3443346125efcf7f9ffdd1566421bff6aa +size 56771 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetBase.uasset new file mode 100644 index 0000000..18ae603 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90a1b2da8d54bd1cbec340ba64c6d9bf50118348e67a090ce036f35d8d96f1e6 +size 20834 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetDoor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetDoor.uasset new file mode 100644 index 0000000..ce7ccae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/CabinetDoor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a69477ea30c64b3a4c2427fa6f83c14135dd8db699a08024fa7f0edeb952ebdd +size 17717 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Gray.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Gray.uasset new file mode 100644 index 0000000..4906592 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Gray.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5522aa366ce2d692e0012d2440f74795abe1ae374e7c38a87c805642c1ce71f9 +size 81208 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Green.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Green.uasset new file mode 100644 index 0000000..de6b2c7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a8838140790a8f0c9b45a47fe6b690c968fefa14881ca723ca28fe28cd383fa +size 94280 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Lime.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Lime.uasset new file mode 100644 index 0000000..3e00508 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Cabinet/Lime.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:085d6332bf28450397ebbc9bd8fce6a136cec6db508843e0a3b436a10e087463 +size 94098 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Black.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Black.uasset new file mode 100644 index 0000000..07912f0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Black.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba8324a8a46e6155cb2a24ada3e678b52e17ec6d8066d0b98fe035815c37cc87 +size 25458 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRecorder.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRecorder.uasset new file mode 100644 index 0000000..ced8b0a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRecorder.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79702223e28a0f8b0251e0bd4e4511483c0ecf9c1a6b5c1255ebb68ec3057284 +size 268529 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRenderTarget.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRenderTarget.uasset new file mode 100644 index 0000000..e39807d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraRenderTarget.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f32afb2d3f030b63aa27285f455371b0191343097d1d98f1a4abd79860fccfd +size 3641 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreen.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreen.uasset new file mode 100644 index 0000000..f339cde --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreen.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8a978d8e277ac685477e77140a5f6b05fb3bfcaeffbf32fb69e7299c1204d7c +size 41802 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreenMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreenMat.uasset new file mode 100644 index 0000000..41d0331 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/CameraScreenMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:903272c3b4530e07619493df888b6f6e37b152ff9d8102fad6e3cecffd942677 +size 10873 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Camerabody.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Camerabody.uasset new file mode 100644 index 0000000..8399df8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/Camerabody.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6426aa8965d838f8d7e3687ede63be7ba029c866339b43fa42cba0984a108767 +size 75902 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/ForeGroundRenderTarget.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/ForeGroundRenderTarget.uasset new file mode 100644 index 0000000..b39d757 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/ForeGroundRenderTarget.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f909b074d1c940bbdcb9c9802758d09d6041b228899315a89826249089675c88 +size 3492 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/GreenScreen_PP.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/GreenScreen_PP.uasset new file mode 100644 index 0000000..f708984 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/GreenScreen_PP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:334a096855afefda3acaa1bcf70eb2bf7e820e81d8bfb4afc39f66d8b9bc576d +size 65017 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput.uasset new file mode 100644 index 0000000..5f0bd9d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a988c604e1874c549b15b7c8453cb95c167cef32360216ddee3167d028176b9f +size 3377 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput_Mat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput_Mat.uasset new file mode 100644 index 0000000..8781aa0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedOutput_Mat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:041a1ea331c26a214ac6732716105ff8dab91bdd795972b470299eca9225f493 +size 95569 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedReality_MPC.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedReality_MPC.uasset new file mode 100644 index 0000000..004fee2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/MixedReality_MPC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:559661367178dcdca61fb03f772e60fc6cb6018940c5c391335de3a1f9628b92 +size 2150 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/White.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/White.uasset new file mode 100644 index 0000000..733fbfc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Camera/White.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e575c89ace5e2d09c5bd99780b595cf63284e352497b74015a5fb4cbc03705f +size 74950 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialComponent.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialComponent.uasset new file mode 100644 index 0000000..d4a89ed --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialComponent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e1a99dd8b3ca2b77246336f1d27da028e603b27eac7f771784f68fe25e3afd2 +size 131681 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicator.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicator.uasset new file mode 100644 index 0000000..5504cec --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1af2ccaa51feb7292eab50373fd1c638172ef7bc2862fe88f0f5eb5f0013300d +size 196537 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorBase.uasset new file mode 100644 index 0000000..5e4d882 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35474a9568c69f94180bcb1123c25a7e4aeda891aa15c7fa65c4b0172e8faea4 +size 16474 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorTop.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorTop.uasset new file mode 100644 index 0000000..b46d0e3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/DialIndicatorTop.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46741e029d1ec16ed7c83772456430b084feb98914228afb72a5a1918848b2de +size 119906 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Green.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Green.uasset new file mode 100644 index 0000000..0ad2666 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45dce261f7182c28d550ceb3a520de2d91c582c6972fff0c1d0be43302cfd43b +size 36251 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/NativeDialIndicator.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/NativeDialIndicator.uasset new file mode 100644 index 0000000..c81658f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/NativeDialIndicator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:802e00e2bd13dc44b65b6622cab36622cd7a2d81b8953d101b84baf34751344b +size 145751 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Turquoise.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Turquoise.uasset new file mode 100644 index 0000000..f1a2768 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DialIndicator/Turquoise.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a6cd83380bf55b9abde582cd88ef63374cdcde8a2f936bc9d4fc04ca3a7d747 +size 39285 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadBolt.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadBolt.uasset new file mode 100644 index 0000000..f60b36a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadBolt.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6cf5a73b1099be46fc9a148dfb2763094df9f0b64763d56c0aee985ff0ae85b +size 30756 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnob.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnob.uasset new file mode 100644 index 0000000..a2087d5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnob.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b661932077b81f5914b0d14503944aee305c467ccbc59df2404b22fb4e57fef5 +size 20444 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnobBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnobBase.uasset new file mode 100644 index 0000000..484650b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DeadboltKnobBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5ed7dac671a4a05a230b858c5054c1941201a2d5192a8a888c5bda08192942e +size 37396 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorActor.uasset new file mode 100644 index 0000000..5e16d72 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce17327cd3d5f8a2a24afd737e596de392359fecc7e613930d57d293e3a8baa9 +size 482616 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandle.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandle.uasset new file mode 100644 index 0000000..d084f8a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef34824e65f8fcc98cbc0b8a38062f592e2601e52b4d3d004d14d4a5bc16de1b +size 27188 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleBase.uasset new file mode 100644 index 0000000..05afe5a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:890004a21ec396b826fb1278c0666608f78736c089e47b7885af438d21e10932 +size 42483 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleLever.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleLever.uasset new file mode 100644 index 0000000..c23ef09 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorHandleLever.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb8d56160493cd0a40c091068c5b492bfaed26c102e435449754cf7d95fa225e +size 65367 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorKey.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorKey.uasset new file mode 100644 index 0000000..696d794 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/DoorKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00e2b7ebe5483e565299514705089198b9d04fd4a76bed5a9ede8792d62fb0e6 +size 408094 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Key.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Key.uasset new file mode 100644 index 0000000..11e5242 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Key.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd857ca55f15461848584b49987fdfb1131681c62be2b5a3440b0fef2f466764 +size 43253 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/KeyBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/KeyBase.uasset new file mode 100644 index 0000000..201b05d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/KeyBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a24c3814a4bb15da918c05eba3728a94898ba82527bb36dd1f2db583ab8dcbe +size 43831 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Keycore.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Keycore.uasset new file mode 100644 index 0000000..c7312f8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/Keycore.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c14d750494e0099b614637dfecfe27116eea9e8e5cfe7ec7c95231d3964dbdc2 +size 23166 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/defaultMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/defaultMat.uasset new file mode 100644 index 0000000..d40dcb8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/defaultMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aacfe5a0b4bc6f62d18c26e216bda6ee8e4c683256878385c7c2cfef7f9f7c5 +size 110540 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_aldoor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_aldoor.uasset new file mode 100644 index 0000000..f6c8903 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_aldoor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5260874df1dde108b2931d679350eb6e821cb645d7747649cb07c3d2ee77a3c +size 21908 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_door.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_door.uasset new file mode 100644 index 0000000..6d43d3d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/door1_door.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4263e50843e1888f724b10d1f4420ee4dc3d1fb8b7ac94ef12b404250ec53c09 +size 24094 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/glas.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/glas.uasset new file mode 100644 index 0000000..8719abd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/glas.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:194000a2f547acfa85ed79129f880db150a504b26246552e62f92a511e5056d6 +size 28970 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood.uasset new file mode 100644 index 0000000..863d589 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1e195f085561bbc017273590f789c4b5e9adca817f7836aba89152d2efc903c +size 31512 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood2.uasset new file mode 100644 index 0000000..9eab57d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/wood2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe79c61941651446755e024ce6a47ea35d3d0f58598a5e3f4e02c35f13035a76 +size 31516 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/woodteak.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/woodteak.uasset new file mode 100644 index 0000000..7ee30cb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Door/woodteak.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91b701891d8e479854277c3a4dbe0f4c521ba2df1dc32ad1a7bd01cbac1624ab +size 2433033 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/DrawerBaseActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/DrawerBaseActor.uasset new file mode 100644 index 0000000..db9dff4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/DrawerBaseActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b22af8781853e2c43144ac2c17cb5a3a43cda1a23151cdb66d8aed2665ad7b9b +size 77028 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk.uasset new file mode 100644 index 0000000..c7bf0cb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87acc845c797c51c77d36f249df27f50ca137ca4e2fbdad94334292ea880fa55 +size 34344 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer1.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer1.uasset new file mode 100644 index 0000000..14718e1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:052e9fa2d2d7844857b79951dfff6dc228fe2c96194c944939bf660772221d50 +size 22293 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer2.uasset new file mode 100644 index 0000000..5e7ce1c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawerBase/temp_desk_drawer2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:346c24d947e981e760abe6c888a1ec69eec72382d862ea2ae698300c7a34f883 +size 22608 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/BaseWhiteBoardTool.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/BaseWhiteBoardTool.uasset new file mode 100644 index 0000000..b206c1e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/BaseWhiteBoardTool.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1b5385b120dae1c65a3b9b1260cd87fcd68d2e45949bb5b3648aa3e145ce611 +size 270932 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/DrawingBoard.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/DrawingBoard.uasset new file mode 100644 index 0000000..5e31cad --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/DrawingBoard.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e5f476146385a2fcb8a1ea30be8c211a18246a38bf3bb7bad7a6f35b7c082e7 +size 125384 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Eraser.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Eraser.uasset new file mode 100644 index 0000000..ee1d1c2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Eraser.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ebb1e920500f60c3603fc5c4ea4ee6b9bb8583471c3d5f3d2d71f4fdaaef147 +size 153827 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Eraser.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Eraser.uasset new file mode 100644 index 0000000..aac71cc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Eraser.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97e35d483072b22159307911c038852fad0b03662f9692614157623bbc8a0f18 +size 15301 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Marker.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Marker.uasset new file mode 100644 index 0000000..ba476df --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_Marker.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:088d9e4a46d0410b8ec1de2f0ec286a018f472537429ba39aa316698eef3f951 +size 31118 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_SprayCan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_SprayCan.uasset new file mode 100644 index 0000000..8cee56d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MERGED_SprayCan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c83a20b95e4f03a274b29badf291d9df18823fb9e23f69332654b10efc80ad2 +size 58204 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Marker.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Marker.uasset new file mode 100644 index 0000000..ea7d2cf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Marker.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e3a7dec1935eb88950bd50ce7893644aca8287f0d0db45dd28de4bf707d8ef0 +size 151638 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerBoardMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerBoardMat.uasset new file mode 100644 index 0000000..fee99be --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerBoardMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a6d31a42a324dd4e72d005062383eb028b68aaacb27139df5a712d59a028139 +size 9469 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerMat.uasset new file mode 100644 index 0000000..d63f422 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/MarkerMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3c5a7a4236ea97fcc5be4c3c595dad5bfd2588857020a6bcbe830c3fbf3c215 +size 94918 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Mat_Eraser.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Mat_Eraser.uasset new file mode 100644 index 0000000..3ad5b0a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/Mat_Eraser.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f380476ebb6ea8b12ea9b8d2b9cc4e696212e39107b2b74e7107714228c4116 +size 85981 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/SprayCan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/SprayCan.uasset new file mode 100644 index 0000000..9ee4d52 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/DrawingBoard/SprayCan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df42bf574eaa21c3b4788bb5453bf55b69c5fd7f7eba1a102aeeb5d5ef866efe +size 152211 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureViewer.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureViewer.uasset new file mode 100644 index 0000000..32a6e37 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureViewer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:952c0bde0af4effcc991bbc910dae104918d5cabd1e2c82af091e871d2dec210 +size 171940 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureWand.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureWand.uasset new file mode 100644 index 0000000..f8b847b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/GestureWand.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f39b3b35bf1867075c1861faf5c36fc09e53209693f48e985f25d6441bf088ba +size 187040 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/VRGestures.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/VRGestures.uasset new file mode 100644 index 0000000..ee529cc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Gestures/VRGestures.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58529d9b47718196ede8460d3e80a9d319af36b502de104f0e970b52c6624c49 +size 10528 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableChar.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableChar.uasset new file mode 100644 index 0000000..c7f5243 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableChar.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6096c920fbcec3f2a7a87f0eb757f6db906eebe236a32b92b0a8c30be2407d0a +size 50949 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableManniquin.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableManniquin.uasset new file mode 100644 index 0000000..fe5f4ba --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/GrippableChar/GrippableManniquin.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc29ab5ef52d0eb01c7863e443bc4c1ba5e1fee229f7031539219f4bfc5fa01d +size 94552 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/HandTruck.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/HandTruck.uasset new file mode 100644 index 0000000..a87804c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/HandTruck.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e50b5b22fb83eea948452c2e776b1e057316c067af2736da2ad18f9dde59caf +size 48570 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/MI_HandTruck.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/MI_HandTruck.uasset new file mode 100644 index 0000000..9e9d95f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/MI_HandTruck.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a8465deb9a30bfe437f77414213a197080177c58e9a1801583c016c62cf4a7 +size 13945 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Body.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Body.uasset new file mode 100644 index 0000000..7633858 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Body.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55dceb9c905187abbb630ae206ca2398fce1d154fe7f1047b2d88bbc4c67733a +size 131100 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Wheel.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Wheel.uasset new file mode 100644 index 0000000..fafb830 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/SM_HandTruck_Wheel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05b34d87202a0e97df3a1dd0998191d1612aa1d3e3e0058ed25b2787f578fbf4 +size 71160 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_BaseColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_BaseColor.uasset new file mode 100644 index 0000000..93a4f0f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e26c48e0e746ce733c7772a3101f1092cd18b26995cf6cd31f990d150c74e125 +size 4370558 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_MetallicRoughnessAO.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_MetallicRoughnessAO.uasset new file mode 100644 index 0000000..90b5d28 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_MetallicRoughnessAO.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83f7dacb84501221919aefa415fd647560835c33cbd7dd72bcf80d3380d2684c +size 4949633 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_Normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_Normal.uasset new file mode 100644 index 0000000..760ce57 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/HandTruck/T_HandTruck_AliOweidah_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b08848526ab3fff7b97dcdff9d64540f46de6f49b8ad3fb7764ffa80c2f507 +size 7035954 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/InteractibleTut/TutorialSlider.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/InteractibleTut/TutorialSlider.uasset new file mode 100644 index 0000000..cc5be48 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/InteractibleTut/TutorialSlider.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ef355113eccb855e9ffac1cb2506f3b359caf156893d5bf2f8b535b0d62ce94 +size 252172 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Gray.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Gray.uasset new file mode 100644 index 0000000..ce69622 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Gray.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f500399c20585f9041847650342b73a32df68dd9fc4eeea74aa6f80b768056d7 +size 32808 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Green.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Green.uasset new file mode 100644 index 0000000..c825617 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/Green.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:582c09c7a5b6b10b2db4d0ab62cf1a123eae396406cc68e79b26bc4843719511 +size 36243 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverActor.uasset new file mode 100644 index 0000000..9f6a7b4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfa6ff0b0de5084fe99620f2fbf997891d914b5254b37b88be27a9eb30bb6b9 +size 222796 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverBase.uasset new file mode 100644 index 0000000..379e507 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42a3bb07b48e9a7d3f936b7e686429341c96e06650c95c3b54d4aa1faa8bddac +size 17163 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverComponent.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverComponent.uasset new file mode 100644 index 0000000..3a6209e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverComponent.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:438604f263cfe5e15e41eeecb95dee988b09f1678eedbcb7144d9e6717e9e832 +size 245397 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverTop.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverTop.uasset new file mode 100644 index 0000000..e05db9c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/LeverTop.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e8b02589009be42fac9ed88b160195d6dbbc22ad20b87d1d5bf55998c58a234 +size 77004 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor.uasset new file mode 100644 index 0000000..127358f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a17a4ef9754e7c8c1b3dd8f963efc63bafe68c8c63f99bb5838b645765851e5 +size 87973 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor_ThrowSwitch.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor_ThrowSwitch.uasset new file mode 100644 index 0000000..669b455 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Lever/NativeLeverActor_ThrowSwitch.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c0b143a789a7c12743c7a0677aaeb9cd63c869f6bd997710023a24ef818ca5d +size 131241 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/aluminium.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/aluminium.uasset new file mode 100644 index 0000000..e827cc3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/aluminium.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e5f45a7f810a4a94b8455fe4652d245e31d71d30378ca4505877721d7d7ba3c +size 29654 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_mate_plastic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_mate_plastic.uasset new file mode 100644 index 0000000..4cbc444 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_mate_plastic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54369a67837f6d040b25cfa951e999d05a17124736b65c2b6897653606230d4a +size 10786 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_poli_plastic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_poli_plastic.uasset new file mode 100644 index 0000000..49d9a73 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/black_poli_plastic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:761abe2dd65aa06e948ce2a388d1914d5f1bdd096ed334ff28182e51eb697123 +size 10675 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/fabric.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/fabric.uasset new file mode 100644 index 0000000..f675c5f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/fabric.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33c0118c19b766a04ce2221316160a1ad5b55dc993e125a084ee3a37cee77d0d +size 41501 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/net.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/net.uasset new file mode 100644 index 0000000..9ba2a96 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/net.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7dc56ef826ad8f792fcf1a164dc40361aa5f8a1c1327f2cdab4e6e2aac91e1b1 +size 37417 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/wireless_vocal_microphone.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/wireless_vocal_microphone.uasset new file mode 100644 index 0000000..f1bcd49 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Microphone/wireless_vocal_microphone.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa8c1bb3639baa0add6a8b0dc37002da24d8ed7e4503fb9be0c1a9358b07c225 +size 3734774 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion.uasset new file mode 100644 index 0000000..e497447 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6f76aceabfbf35f0acd3c0d1c7193472fa8b75e3c9da7827d4ee4208ec6a3ca +size 70071 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion_Inst.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion_Inst.uasset new file mode 100644 index 0000000..cbcd874 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PPOutline/PP_OutlineCustomDepthOcclusion_Inst.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6e1239e4c895b808e8dd2bb2be41d0814d82a3e5ad832e199e65b3fb865fe10 +size 24471 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PickupCube.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PickupCube.uasset new file mode 100644 index 0000000..8a90b37 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/PickupCube.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2def2681e56880ae4a57c5059c4dbe614d2ccd61f636567526124246bd090ea +size 31105 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Potion.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Potion.uasset new file mode 100644 index 0000000..eb74bee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Potion.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c83598eb5ca29960b40b59fb57e4ff9c5f93d3c375c6e81e2c5a6b839f30e1b +size 73721 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionActor.uasset new file mode 100644 index 0000000..348da12 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8ea4d400a5353c6deaaef479e751bb8ad4637529c0d624334dae075082bb5ee +size 287635 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionMat.uasset new file mode 100644 index 0000000..9063951 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/PotionMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f152c24befbe476276a7ad62a7d0d36ca359e93337ad4212b6c816e8df53a801 +size 10279 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Stopper.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Stopper.uasset new file mode 100644 index 0000000..a7d9849 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/Potion/Stopper.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0672aa3b1222e3cc5bfe2ccc381f2cfa5fc69c67caa6b789727ee8a9a6b9a40 +size 33960 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SelfGraspActor/SelfGraspExample.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SelfGraspActor/SelfGraspExample.uasset new file mode 100644 index 0000000..ddb6a34 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SelfGraspActor/SelfGraspExample.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73668b1ac902a37543284fa7b3cbf3adc2b0ad444eff101370a45867e6403d51 +size 138631 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapActor.uasset new file mode 100644 index 0000000..564dfe2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7369ef195fb24315f8040a71d8183a122d7554ff4583f7a9641e35f76908d52e +size 174114 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapPointMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapPointMat.uasset new file mode 100644 index 0000000..509c299 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Misc/Examples/SnapPoint/SnapPointMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb1df872f07f671841aa1679f62038b5c62a380627cd8818eb15edb552474ccd +size 74020 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_MicroDetail_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_MicroDetail_01a.uasset new file mode 100644 index 0000000..3e3dfcd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_MicroDetail_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46de3f6d9eda13a259e2ce89e43509e9e2bbaa2e348b18351084caa581b9ae10 +size 130061 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_VertexBlend_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_VertexBlend_01a.uasset new file mode 100644 index 0000000..56fd432 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MF_VertexBlend_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eb1a3a7be5ef4967b926fcd0438b067d007d36adca9c1bde966ca02832812e2 +size 53424 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MI_VertexBlend_02a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MI_VertexBlend_02a.uasset new file mode 100644 index 0000000..865299b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Functions/MI_VertexBlend_02a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dfa4ba4f6e7f4468bb26eb5bc081529e6901a923d55b5054736bc7bcec928b5 +size 101357 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_Cinderstack_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_Cinderstack_01a.uasset new file mode 100644 index 0000000..60f79e0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_Cinderstack_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f2c6c9ce1f1e5201bd89b74ca29bc67d735902d6f33ed901af562b187ddb7a2 +size 130236 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_OilBarrel_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_OilBarrel_01a.uasset new file mode 100644 index 0000000..fc56967 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_OilBarrel_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:237757931ae10a9178c92453c455aad9a514beabb2a36c650e55bec6ad004123 +size 113923 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_WoodenPalette_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_WoodenPalette_01a.uasset new file mode 100644 index 0000000..4149d6b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Instances/MI_WoodenPalette_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b18ff4d445db1dd7afe92ac44c4c9cb6054b4a99d37ca31803cbe6a1d6637ef +size 117766 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Background_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Background_01a.uasset new file mode 100644 index 0000000..0e319ea --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Background_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aab16f3516b9f21762acd32f5b741a4332026fe5b19e0586202bce373c85f291 +size 160378 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Decal_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Decal_01.uasset new file mode 100644 index 0000000..9ca51b8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_Decal_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56b5d41d00b73ca83606fc1a20821b572c464085807de61199ef537800e28ff3 +size 92078 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_MasterMaterial_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_MasterMaterial_01.uasset new file mode 100644 index 0000000..121d8bd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_MasterMaterial_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a41595af710b517ff91f9fe236ab3ac6925b1016df38fa902a6d57944aa8d76f +size 152973 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_PP_Sharpen.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_PP_Sharpen.uasset new file mode 100644 index 0000000..81aa4bb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Materials/Masters/MM_PP_Sharpen.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bae9c42636782d7be44a833b5696bd0d1c09c47cf23bd89c536cdcc50d457c4 +size 97284 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_Barrel_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_Barrel_01.uasset new file mode 100644 index 0000000..8529cfc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_Barrel_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee089330d04e55771019df07689a86a66c1bc1fed294ce1480d1becd5037463f +size 53719 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_CinderStack_Single_01a.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_CinderStack_Single_01a.uasset new file mode 100644 index 0000000..18404af --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_CinderStack_Single_01a.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7350a8a119cf53cf3de4e15b06bebfa8f6c93bdbb335949a4a10f891e4cf2c2c +size 22573 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_woodenpalette_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_woodenpalette_01.uasset new file mode 100644 index 0000000..32eaad9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Meshes/SM_woodenpalette_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f87ea3210fe0d335d0d36f93b53b06d4398880aedd846e9db8817c54d29a1f1 +size 67207 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_ALB.uasset new file mode 100644 index 0000000..a9a9934 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:168d5ef3074c9c661e459c699daddbd5c583fe93a0a62d27cd074da5ae312e80 +size 2543556 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_NRM.uasset new file mode 100644 index 0000000..29a77e5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c428772f2f3ca07677e5a50c64c2d3db5342d312b93135aa72d40dd3ac2f6e4a +size 3155023 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_RMA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_RMA.uasset new file mode 100644 index 0000000..1d52381 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_DebrisTile_RMA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:009179a5932b69793956fd253e44932b5b1b99821dce2a6fd3c4c74f42c2e2a2 +size 2240008 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Dirt_01_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Dirt_01_ALB.uasset new file mode 100644 index 0000000..c8b4b55 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Dirt_01_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:357531740d3fdd72f7e6be6396b9ed30333a2a5198a4d7554bd8e6211b9696f7 +size 2866443 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Micro_MASK.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Micro_MASK.uasset new file mode 100644 index 0000000..e74e684 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Micro_MASK.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2b02bd7ba793d75f52a6e05e13c3e0ff0f391b3cbc3be418c34d85cbb9116cf +size 564567 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Noise_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Noise_NRM.uasset new file mode 100644 index 0000000..83d1950 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_Noise_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:864e9a4b2fede4c864c1dbdfae0af968544381e52dcdc7fa083509288737ac27 +size 660264 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_ALB.uasset new file mode 100644 index 0000000..f345efa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8714f0cbb121f613b39461bafb3d09d66307ba072e70f40aaef18f2510148e5 +size 8390576 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_NRM.uasset new file mode 100644 index 0000000..7e26d17 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47dbb52bd3eec831e7d501e841f908770964d44784756130aa817efa741c9aa1 +size 10932150 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_RHA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_RHA.uasset new file mode 100644 index 0000000..7829a74 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_01_RHA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcfac76b178dc5355495ad83b1f19909174e1d869cd189eebf3aa19785232a4d +size 7534879 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_ALB.uasset new file mode 100644 index 0000000..d7573fd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:816d326b9e476fdb06364a6d10243e9b2a2caa0560feb4e691c6e77faaa47b66 +size 7202171 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_NRM.uasset new file mode 100644 index 0000000..ef620d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1fa9075b4b85acec31277ac058b8503e453c862744d0033aba76a80832066dd +size 11008277 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_RHA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_RHA.uasset new file mode 100644 index 0000000..b8a0d67 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Tileable/TX_RockyMud_02_RHA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48e7a11f5e330dc29c7f1dbd9bc38678ca1da39d1a7dd64ba2c36242a91c540b +size 7413211 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_ALB.uasset new file mode 100644 index 0000000..5735871 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2cbb7c7de684ec63939ab6b94aaed76fc7b15dd66e37f4c65202f43928b594a +size 5091292 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_NRM.uasset new file mode 100644 index 0000000..a4c0bdf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87e5010e03419020639deafb5370c430a545f36e76fcd6d50f1f3ecb6d06af22 +size 6858453 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_RMA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_RMA.uasset new file mode 100644 index 0000000..b89fba7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_CinderStack_RMA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5dd9080c43668d00a4f4331dd2182ea677f081bd344618a02b24046fbec17dc5 +size 3624022 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_ALB.uasset new file mode 100644 index 0000000..fc98634 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13b4702228bd042b016f87904dcb6014a6c0c9dea23a2a4ad33b996c8ebca385 +size 3661816 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_NRM.uasset new file mode 100644 index 0000000..49b84ab --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a2ea530056a112fda331213d76faf4dbcc5dae2038164abd627b7200b164e7 +size 6255274 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_RMA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_RMA.uasset new file mode 100644 index 0000000..7d8c000 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_OilBarrel_01a_RMA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d61bd788260e36eb91619246519745837fe90bd3f501916d57b3a6a71cb87b8 +size 4853191 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_ALB.uasset new file mode 100644 index 0000000..31e27ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8252f39e2986b4c9cd3cf9ef868867bfd192680799ea67fa40ffa0b116d4a21 +size 8132717 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_NRM.uasset new file mode 100644 index 0000000..461c99b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01d00655b7100c23df0a97c9d225cda5362118d51b8feedba4a2fe60685e4f41 +size 7648965 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_RMA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_RMA.uasset new file mode 100644 index 0000000..014c3fd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Unique/TX_WoodenPalette_01_RMA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd149ef586ec4d096fed5818ebcf22f573ab3da822555a4f1e708558b4d94fc2 +size 5332763 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_ALB.uasset new file mode 100644 index 0000000..d467fb5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e724c834c9f2bb6e66836eab618145664979312615acf36eab6c9ea1609c2b8 +size 5323994 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_NRM.uasset new file mode 100644 index 0000000..62e011a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Backdrop_01_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecfd969052860ea9ffd796813294496cbf5ae851d58a543797d8748a7f8ffbf0 +size 5096528 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Cubemap_Stairs.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Cubemap_Stairs.uasset new file mode 100644 index 0000000..9ddb474 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Cubemap_Stairs.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:306378ec3fa39fa39e98c4df608aa75b7e451b3bc461ebecfcb5f2051bb61a55 +size 1344026 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_ALB.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_ALB.uasset new file mode 100644 index 0000000..09509a1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_ALB.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a659af51fb2279a433998785e28c10f59def76b27e954fd40ca25d80dd43116c +size 4630 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_NRM.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_NRM.uasset new file mode 100644 index 0000000..2a45970 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_NRM.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24787f2f3893798b33f605d491ab1911c1d49629e47343def2a6091cee78450a +size 4971 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_RMA.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_RMA.uasset new file mode 100644 index 0000000..516056a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_Fill_01_RMA.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78f0959e9ea208f1ed78d4b04f9ce8ce6485889c632ff53f606571a57ee9efcb +size 4791 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_LUT_01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_LUT_01.uasset new file mode 100644 index 0000000..f34d476 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/Construction_VOL1/Textures/Utility/TX_LUT_01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dac0ed2527081ae69997a0494d54893218beea1282af9b10319881781c364006 +size 9379 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/PhysicsProp.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/PhysicsProp.uasset new file mode 100644 index 0000000..3052b27 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/PhysicsProps/PhysicsProp.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2cd253b479208691a09327dab304cb56c9947d0c684ec157338c366130505049 +size 26825 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/BaseMaterial.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/BaseMaterial.uasset new file mode 100644 index 0000000..7944bb2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/BaseMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79fa7371413bf0625027b539f441ce9d07dd32b70bd1dcf92c159e3ed558edac +size 37288 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/M_FPGun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/M_FPGun.uasset new file mode 100644 index 0000000..69030a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/M_FPGun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88069a4e4ddaed5a467d821cae74ab0cf59eed443b75fb98172fcbe6cf9b3b32 +size 42891 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset new file mode 100644 index 0000000..dd89f69 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_GlossyBlack_Latex_UE4.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa1a7361d2e8419ce214d353423f6dc2f75b0a2fe94494454cf0f234be295b83 +size 11035 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset new file mode 100644 index 0000000..55f57a2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecca6588006834428240b3da4420d1d4bb38b065d50c4fa169a5cfb31de7dcbb +size 16411 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset new file mode 100644 index 0000000..4ae8002 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Plastic_Shiny_Beige_LOGO.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02b4ba08e261bc3f8d711c1bb4f1d0581fc8714838500a19db047f9663f31918 +size 9346 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Screen.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Screen.uasset new file mode 100644 index 0000000..fac8e9e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_Screen.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1408f030db090d64b88848af82bdd4c7143933c643b1869b7c3aeb0474abe42e +size 5637 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset new file mode 100644 index 0000000..001cc7f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/ML_SoftMetal_UE4.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cbacdbbfe28e1a92b5550db757a640f0ed8afa8a7d61b0e1b588ba12e2ae1d3 +size 20834 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01.uasset new file mode 100644 index 0000000..2588b4e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e74702634949dc793b5faf3362cc480ab86b35e4cd1933df639399110085bec9 +size 8630797 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset new file mode 100644 index 0000000..a27a4f8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Aluminum01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c96051a4f0f700cf03578b2c132cfc01af83bb842e58937abef2ea8f9f5a742 +size 4395348 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_FineRubber.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_FineRubber.uasset new file mode 100644 index 0000000..bd184a7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_FineRubber.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0638dbd563e69ca689c8406ad9ef1ec699a511ddf6d160303f5135d4f5ffaf39 +size 6312755 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset new file mode 100644 index 0000000..b0221a7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41d350d266f5b5524e89f6442adb74532902201e8a78b43024bafa833f1a5e72 +size 5404974 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset new file mode 100644 index 0000000..f655151 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Materials/MaterialLayers/T_ML_Rubber_Blue_01_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d9df78b7114730c1f6aeba1f3cf33f2cbf6c4af1ab932b8169d8cc6445fb1c4 +size 4850545 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun.uasset new file mode 100644 index 0000000..9c7d0ee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3e47ace89b14dfe90e808c925f5f226ddebbfd64fa4c3c83ea29a71c41d45e9 +size 1272029 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_PhysicsAsset.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_PhysicsAsset.uasset new file mode 100644 index 0000000..71dd339 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_PhysicsAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:822e4aecb276c98ae7ff96a36762c6a48540f883a36ba5ed2364bce0d6d8bddc +size 7543 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_Skeleton.uasset new file mode 100644 index 0000000..e28638a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Mesh/SK_FPGun_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3341d663057a271a84a00e1a28615d826c51e56f77587611ccd149e5fee1a73c +size 6626 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/SK_FPGun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/SK_FPGun.uasset new file mode 100644 index 0000000..14f3daa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/SK_FPGun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5494698f9b66bb69ea99a4aaae9fa48de02a38606722b0976f5fc317f1197b8a +size 396510 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_M.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_M.uasset new file mode 100644 index 0000000..c6f904a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_M.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:197e0dc9e1bb8a5fec6101157aae681370f2169f2f0344cdfe31568a02599581 +size 227728 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_N.uasset new file mode 100644 index 0000000..9ebb3f6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FPWeapon/Textures/T_FPGun_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0b556925a72756d6bcb23f1e18bc141321515297ccbfd3ac33d6d4e59a55c19 +size 2044097 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectile.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectile.uasset new file mode 100644 index 0000000..b3dc820 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectile.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:750523053f57c80de4ef91f018b8c64ac00a34763df68bcaaab8d88a152333f9 +size 54602 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMaterial.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMaterial.uasset new file mode 100644 index 0000000..5e23e32 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:090191e27cddafbf08ad68b2c5c9490f93610e27901ec457ca7b29a7e80b219a +size 39588 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMesh.uasset new file mode 100644 index 0000000..e2241c1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonProjectileMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:775f9c72d050928647dae6357e1e66b6bfe05614c070d3aff70a70863a7df31c +size 32065 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonTemplateWeaponFire02.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonTemplateWeaponFire02.uasset new file mode 100644 index 0000000..bfa3993 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/FirstPersonTemplateWeaponFire02.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c08f6393f81a69f838aa1411fa245a9eb3ab8ee60951231772e9d67454e290d +size 306470 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GrappleGun.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GrappleGun.uasset new file mode 100644 index 0000000..6a02069 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GrappleGun.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:001e2ee5be3a783ad1c3798a034bcb9f25ce91be78edb5cfad15164ff7c92777 +size 540085 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GunBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GunBase.uasset new file mode 100644 index 0000000..666a0b4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/GunBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5f4552703e45ded80a16d305a181f7638a2b68d5d60ef2e7156f5114a19f9c5 +size 818943 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/M_FPGunGrapple.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/M_FPGunGrapple.uasset new file mode 100644 index 0000000..963e6c0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Guns/M_FPGunGrapple.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2643ac81668975dc59ed047333ddb39d3be54898134c4e9fa297eabc83650d6b +size 25721 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword.uasset new file mode 100644 index 0000000..3a862a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffc54e3fbe51f9f0cd1552bc7d2de49005aa6f0d79bfb3a8523ad661222bce2a +size 298673 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword_Phys.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword_Phys.uasset new file mode 100644 index 0000000..fef389e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/HeroSword_Phys.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e2e7dc0f58196b8fd57fa46f4b3737a537a3029c209e3d95c4009f74d230d94 +size 95578 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/M_Blade_HeroSword11.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/M_Blade_HeroSword11.uasset new file mode 100644 index 0000000..6d962d4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/M_Blade_HeroSword11.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c91cc03d4a17c922e55388505f2abd289e45081e8d925357ee768877ed1249c8 +size 107825 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/PhysMat_MetalSmooth.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/PhysMat_MetalSmooth.uasset new file mode 100644 index 0000000..f251b74 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/PhysMat_MetalSmooth.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5219da2c8ab4269c5234d11e76e7b5d793c39ba8eb7da2c5dec12bd5b6ae2d19 +size 1522 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword11.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword11.uasset new file mode 100644 index 0000000..f590fdb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword11.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a03c3f0e0f3de7d898c6ea025d720c7b904db99cb4fae52b867aebf973ba3b9c +size 196668 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword_01_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword_01_Skeleton.uasset new file mode 100644 index 0000000..d8ec25f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/SK_Blade_HeroSword_01_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:815ea9b78e76cfb1b4348ae3a97cb7bf640c4fbd4e71d1e69bfb7e8369491c38 +size 5625 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_D.uasset new file mode 100644 index 0000000..8b4fffc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9d43f1ada74559bafe97e50bef7ea39b0215155a4d9a89621522bb499ca5ca6 +size 198578 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_N.uasset new file mode 100644 index 0000000..eb26f19 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_011_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0faac10cadc67807ce2653e847769c7675955566c5feb932195802d4c8e52693 +size 5443 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_012_D.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_012_D.uasset new file mode 100644 index 0000000..3913837 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Blade_HeroSword11/T_Blade_HeroSword_012_D.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86459d73ffac44b0dde0fcac8bac5cddc9280e3c3e700763e16277c39b4d61c2 +size 208578 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Dagger.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Dagger.uasset new file mode 100644 index 0000000..c0089c3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Dagger.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:840ca0e72c6aa513712ff9304b3c47ebe7aec55e7e09cb1f0abb44b098b527d2 +size 52177 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Mace.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Mace.uasset new file mode 100644 index 0000000..9009ccb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Mace.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85a8e4a238d39976d637c001e248e1ca994e6432e1055b4c52027df0ea1c9db8 +size 51924 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/MeleeBase.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/MeleeBase.uasset new file mode 100644 index 0000000..aef5ed9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/MeleeBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85a8f370b1c18dfaeb64365eea8477f366c29cf41229d96fa1414b7fe71171d3 +size 611254 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Shield.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Shield.uasset new file mode 100644 index 0000000..2e844a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Shield.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:960db53865588c3a3896544a9c3a82286dbe7d15f8cca3ad2fd7f9656ab80fb5 +size 167573 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sickle.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sickle.uasset new file mode 100644 index 0000000..9d3b338 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sickle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30814a4a78240dbb3dc9489a4e97b896f42ad19d60ad8ad758db02316806de90 +size 62253 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Spear.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Spear.uasset new file mode 100644 index 0000000..1a3e729 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Spear.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b02f64e4c172901175aa697f0dff10befaae01da8b14542677225371361b370f +size 54722 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sword.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sword.uasset new file mode 100644 index 0000000..197ed02 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Sword.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:498812318079cda021d0746cc0cc48bc51d0717a9f22c4a6070525bcc468737d +size 52842 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/BP_GodRay.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/BP_GodRay.uasset new file mode 100644 index 0000000..6e3d012 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/BP_GodRay.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2a46851c9932dd8d26ee0716b257d373c5172e1042279f1deccff8a2c9c1574 +size 183248 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay.uasset new file mode 100644 index 0000000..f341bf0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ee58fc59456ce4bb6e5fa0111232980474d4ffec3c0aa8571241f93f3f41849 +size 114319 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay_Inst_Animated.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay_Inst_Animated.uasset new file mode 100644 index 0000000..b2c102a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/M_GodRay_Inst_Animated.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:475e9d6c1c455ae69a9ee605fb75e4d5c33897142ae08f791c57ed17dbe15abd +size 87108 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/SM_GodRay_Plane.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/SM_GodRay_Plane.uasset new file mode 100644 index 0000000..9ac7995 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/SM_GodRay_Plane.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d196272fdce50a2f28f8c84ab1783ff598acaa8f7055399d0024e16a520fce9 +size 13530 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask01.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask01.uasset new file mode 100644 index 0000000..4c2feae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask01.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7626534fadbc2771a3aa9b9d4e5053cc9ecc06a1756ffdf21b4a4704928c794 +size 300670 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask02.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask02.uasset new file mode 100644 index 0000000..401423a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Blueprints/Lightshaft/Lightshaft/T_GodRay_Mask02.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06b2668cde292942d34ad8ab348419b9b1467a55cd419a15ea384523177744a8 +size 92433 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_Fire_SubUV_11X11_Noise.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_Fire_SubUV_11X11_Noise.uasset new file mode 100644 index 0000000..78487ee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_Fire_SubUV_11X11_Noise.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5404e7a40c21f0941e2c0eec547e5b211e7afc7e8a0b5c0f79dc10f3644a7d7 +size 111655 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_HeatDistort.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_HeatDistort.uasset new file mode 100644 index 0000000..3b8f45f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Fire/M_HeatDistort.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fd51c9653bd123a8dd16b169497bfc8a4f9ee802f2919429ab7a687cd2ec214 +size 96107 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Flares/M_FlareRound.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Flares/M_FlareRound.uasset new file mode 100644 index 0000000..916197d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Flares/M_FlareRound.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:766c8e34dbc04de89a7fdc9fcf6f7e5bf4cebc9b94e82ca1928574a8e28c2646 +size 98203 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Sparks/M_Spark_VelocityLerp.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Sparks/M_Spark_VelocityLerp.uasset new file mode 100644 index 0000000..5f67139 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Materials/Sparks/M_Spark_VelocityLerp.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0af6253a894c43bcc8bc87af60fc41cf6e6b8ac7dcbdee0bcc75e1219b3bae38 +size 117146 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Particles/Fire/P_TorchFire.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Particles/Fire/P_TorchFire.uasset new file mode 100644 index 0000000..9bdf9a4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Particles/Fire/P_TorchFire.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:254759d62fe73fb2c58112f65980653bb8039d31ccdabe2764c124dfd806e407 +size 191472 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Fire/T_FireSubUV_11X11_32.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Fire/T_FireSubUV_11X11_32.uasset new file mode 100644 index 0000000..881845b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Fire/T_FireSubUV_11X11_32.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:550111515d289af09c05a3415074c6a9c8199262f5a749698e78e5f7f87da302 +size 557282 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Flares/T_FlareRound_Noise.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Flares/T_FlareRound_Noise.uasset new file mode 100644 index 0000000..2a4261c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Flares/T_FlareRound_Noise.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:989c52dec7176c3d608127b383fab088d65cb6343d225cd9129c40f343ddb654 +size 208151 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_HeatTile3_N.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_HeatTile3_N.uasset new file mode 100644 index 0000000..3d8f564 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_HeatTile3_N.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11b0683b7f46609d570f48f7deaebb062261e0f51084f65b280e8e1af8c8f7c8 +size 856610 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise03.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise03.uasset new file mode 100644 index 0000000..9ccd9fb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise03.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d75511ea6c1cff25e9f5ace144dd4a5979700f54999053f0ee3bdb80b373cd1c +size 3405393 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise12.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise12.uasset new file mode 100644 index 0000000..2dacd6f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise12.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:378f11161767fa352819e7379e629444818d705455799b55f177ed082a392a03 +size 4373838 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise16.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise16.uasset new file mode 100644 index 0000000..225767d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Noise/T_TilingNoise16.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d464fccf7965a757438468b30a0284ad304d3286482680d818f89a6680acd485 +size 406330 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Snow/T_Snow01_Packed.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Snow/T_Snow01_Packed.uasset new file mode 100644 index 0000000..80db722 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/Textures/Snow/T_Snow01_Packed.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6d9b5978040bf798bae763b407706b33d80799112cd0a289338cd214a4be071 +size 71605 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/VectorFields/VF_RandomNoise_01_90.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/VectorFields/VF_RandomNoise_01_90.uasset new file mode 100644 index 0000000..5caf55d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Effects/VectorFields/VF_RandomNoise_01_90.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26f05d94150b20d5243039e8a131f8247b3815a90c0d3eb8f09810a836ff17d2 +size 2099406 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/M_Stained_Glass.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/M_Stained_Glass.uasset new file mode 100644 index 0000000..dceaf95 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/M_Stained_Glass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fb023fe018378ff1a51b7e6e466f83d1fcc2a7d1af4223f16ab04bcf9091d66 +size 108387 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1.uasset new file mode 100644 index 0000000..f7d8b24 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59e46a0f80b43bb4a932358a636677c731f230d4b1e84c20d4c25823d7ccedce +size 153667 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1Dirty.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1Dirty.uasset new file mode 100644 index 0000000..76c494c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_1Dirty.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd36ff8ce6303a0f01569b0d85953a46005f369cb6cbefb7e5c0b1c06691bbca +size 167960 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2.uasset new file mode 100644 index 0000000..d7b9048 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:097117c1a29ff25bbcd612d34b4c5af58fd06b28da930763d6e6f27bb526557b +size 141726 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2Dirty.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2Dirty.uasset new file mode 100644 index 0000000..3cb63dc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Materials/Weapons/M_WeaponSet_2Dirty.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc591651531e983b85ab41dfb80c4b2a14983226122b51b28ab0900e3778a75e +size 159444 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Axe.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Axe.uasset new file mode 100644 index 0000000..07bbfc3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Axe.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94187dc99d52e92126399a465c9eb439e6c4e878f2c1c124432e59dbaf71344f +size 128989 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_1.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_1.uasset new file mode 100644 index 0000000..e0ebc4c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e3b6a414c337179dd48c59d919a707a9918eee407cd96c6ceea467f760481dc +size 124408 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_2.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_2.uasset new file mode 100644 index 0000000..3f937e5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Dagger_2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d56bc1f8bc9f88cf44dad20744972f6b7de11d6180240d1209bcb0a5d84d8ff +size 51054 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatAxe.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatAxe.uasset new file mode 100644 index 0000000..f14773f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatAxe.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f89f5bb96bef13e5258c43d99834ad396bad2bf08cbd5913dff95bb67be2865 +size 143225 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatHammer.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatHammer.uasset new file mode 100644 index 0000000..aabb4e9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_GreatHammer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7481a49577f6e195a74ee5fc7bb51e4d72e422bbfc812f04ad18a648c7588ac1 +size 139673 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_H2H.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_H2H.uasset new file mode 100644 index 0000000..cf387dd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_H2H.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e490bdae2c930f9b91f20f70c0b1a097dd1548c81e38f5afde78782d34357cb8 +size 125392 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Mace.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Mace.uasset new file mode 100644 index 0000000..19d2eb9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Mace.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5a09b7b8f984e2a6417cf46c0dd13a9205b72bd7463710396fbca42f534f947 +size 44908 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Shield.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Shield.uasset new file mode 100644 index 0000000..a136b85 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Shield.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:860e01527b514d992288b9d246950454461a910e9c8a3110b2e5b8ea0faf8e1a +size 97265 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sickle.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sickle.uasset new file mode 100644 index 0000000..8f76040 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sickle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14b188a7e76b7070aaeb079d57cfdf8ca773c9b6be50d55602abd840b0d67942 +size 42760 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Spear.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Spear.uasset new file mode 100644 index 0000000..9c8a943 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Spear.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff7bfe5730fba4cdac3157941d7e112e6d97911770b9e1cd06fb677569776a5 +size 32777 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sword.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sword.uasset new file mode 100644 index 0000000..378ed58 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Sword.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb798aaae1c576f72e26eb1da88d16d27cefe5cf956e6a5b6f1cd2bc941413b7 +size 45619 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Throwing.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Throwing.uasset new file mode 100644 index 0000000..e76eeb1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Mesh/Weapons/Weapons_Kit/SM_Throwing.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ab8a56f350b2d6fca2ca70799fa8d7d221f50ec91886c343603488128ef33d4 +size 124904 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_BaseColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_BaseColor.uasset new file mode 100644 index 0000000..9812cf4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f27231b3a3f97e9e2b1c7a2527f963ece0d03a533e19e12a1148af4eca911b76 +size 22201596 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_Normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_Normal.uasset new file mode 100644 index 0000000..f0ead8b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b315359708284f52047eb6e035476c8e8118dbb0fb87aab102195b5635a31091 +size 27731639 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_OcclusionRoughnessMetallic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_OcclusionRoughnessMetallic.uasset new file mode 100644 index 0000000..ba2fea6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set1_OcclusionRoughnessMetallic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc57a2e130aa460d49e4f091be7186e7e94e09dc2a3d03c42f9ebc8ed8316e80 +size 21668041 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_BaseColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_BaseColor.uasset new file mode 100644 index 0000000..f19494e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6757304bf199367136e78bef88c47552e969c43618a6baedd3bac2ca548960b8 +size 19537489 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_Normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_Normal.uasset new file mode 100644 index 0000000..41f23c1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eeb8f00cf65eb3a673d6d12fa86d16b778ef3696fde482e19f2c319d2870f5ce +size 28237997 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_OcclusionRoughnessMetallic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_OcclusionRoughnessMetallic.uasset new file mode 100644 index 0000000..cd867e0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Dirty_Weapon_Set2_OcclusionRoughnessMetallic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6926ed6959a55f20091c3c7693705f73f17a431168fc697a35f08b93d00e453 +size 21919418 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_BaseColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_BaseColor.uasset new file mode 100644 index 0000000..e2646d2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e96bd7a5ffb230b013ce3a73491411323ddc32e99a5646b0fa177aa55f68eafa +size 16301026 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_Normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_Normal.uasset new file mode 100644 index 0000000..6931fca --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3820cbf36d6cef92cb5414d316bc6a90e2c01ee4b2d4b8adee885a2c9c118d35 +size 24691345 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_OcclusionRoughnessMetallic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_OcclusionRoughnessMetallic.uasset new file mode 100644 index 0000000..4eceaec --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set1_OcclusionRoughnessMetallic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3eaf8634a3d32fa06a9440dcd4dfaf13e2dac678d2dda181e94d4f1ba7edd83 +size 18813193 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_BaseColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_BaseColor.uasset new file mode 100644 index 0000000..0a0753e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:210eb6bd9c4f3120a7f97616b1033cdbbd6cea346739448b18997f9f2064f201 +size 16278172 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_Normal.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_Normal.uasset new file mode 100644 index 0000000..0488c33 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6887a707b1c9defcf84ed80247061f23a53d3931789cb1f36f1fed1397d4c8bd +size 22453854 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_OcclusionRoughnessMetallic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_OcclusionRoughnessMetallic.uasset new file mode 100644 index 0000000..6dbdc00 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Melee/Weapon_Pack/Textures/Weapons/T_Weapon_Set2_OcclusionRoughnessMetallic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abd37e4193e561e39c3377ce3529db87a93b577bece881b952eef0704aa76bdc +size 22522560 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Cylinder1.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Cylinder1.uasset new file mode 100644 index 0000000..c7c00b0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Cylinder1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:768fbb4f48589b55c7260e17e2c9403eda7b754b70894221f086d76df6e7e2bf +size 33387 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Dummy.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Dummy.uasset new file mode 100644 index 0000000..6a0288f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Dummy.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f46b9fa8ed6fc037539117502b6a6926f0a35ce2923bbc8572abfa618a8ce52 +size 42603 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Default.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Default.uasset new file mode 100644 index 0000000..e401673 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Default.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:932cdd3bb9fc0a6a2e2c7859bc24e10ac79943769e7585a42e3fa09892970f7b +size 101948 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultg.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultg.uasset new file mode 100644 index 0000000..1c1f60f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultg.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2af53bc782c41a75638f767cc0e425e395f71221c8766e31395ae8c5bcfc5db +size 95583 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultgw.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultgw.uasset new file mode 100644 index 0000000..5fb009b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/01_-_Defaultgw.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f49205106745bca0b66a4fa65fc9dc896477ab180a014df7f7b423db84b34ea8 +size 51417 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Sandra_s_Sword.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Sandra_s_Sword.uasset new file mode 100644 index 0000000..a18f9cf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Sandra_s_Sword.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e08448fd62c79e3e4f6ba35e0d0f06aa3ea00ba8a4a099132bc1ec12ef873b7f +size 251353 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SliceDummy.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SliceDummy.uasset new file mode 100644 index 0000000..eacf3d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SliceDummy.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:951117b745ace00e670c2a02075f91b1fd6d494563ccf9b3e8a35b86bc2c40a1 +size 26921 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Slicer.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Slicer.uasset new file mode 100644 index 0000000..1a43812 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/Slicer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b7b75445dd797b1c0a197c952063eb6055403ec66707f1a4d204764854cfeac +size 162371 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SlicingDummy.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SlicingDummy.uasset new file mode 100644 index 0000000..82f2485 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/SlicingDummy.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae36e55d206fb080cce3ba8d816e28d891ce507c8fc4b4ade937acfb2a1dacc4 +size 216357 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/defaultMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/defaultMat.uasset new file mode 100644 index 0000000..e9382ba --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Grippables/Weapons/Misc/Slicer/defaultMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b61bb60f00f4342daeb6da43b2e73f8d0b3b25411d59638ccc419151fcc39ff4 +size 59089 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripLeft.uasset new file mode 100644 index 0000000..a3ecb0f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e838ac7a16a9abfbff653a00a2b541db9d5039649b8962d867bd271f673354b +size 1733 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripRight.uasset new file mode 100644 index 0000000..4a50b4b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/AlternateGripRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b709af759ede67d0f1be1236ea9d32414abd47c8aecdf0af30d81518712544e5 +size 1742 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementLeft.uasset new file mode 100644 index 0000000..7434073 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efb29e43077a95e58dc4701bac014adaeaa5a6aa59bbfbe67558023152a4b08a +size 1925 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementRight.uasset new file mode 100644 index 0000000..6cc857a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/ControllerMovementRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47cbe9c2325bfce551802aece2a33050ab71b31ad010a0a4c03fed01ebf4403e +size 1934 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Jump.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Jump.uasset new file mode 100644 index 0000000..de27f0f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Jump.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1a5b728c4a1fb21be95307d7ce48439155eae8ff6b791f877d3309b8a62b29 +size 1645 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/LookUp.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/LookUp.uasset new file mode 100644 index 0000000..dc3e867 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/LookUp.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3be15eb111e1907782872e423d45b0b6f441e710d0c144b4fdfbb0b380d1e8ec +size 1810 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveForward.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveForward.uasset new file mode 100644 index 0000000..3e3270d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveForward.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c52c5253dfbb4a1ff7ba50eb675b199b0999f5b704c34a6a4440114763e2426 +size 1857 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveRight.uasset new file mode 100644 index 0000000..d44788b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/MoveRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd6d29db7763b629a4e8588db43f677b0cae53855cee720530d918b0339556df +size 1837 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Turn.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Turn.uasset new file mode 100644 index 0000000..0a7106f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/FPSActions/Turn.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2cef6554d211f2da6161b97663aeb2369c648f9ede8db34c919575257c025baf +size 1792 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamLeft.uasset new file mode 100644 index 0000000..65cb30a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9197c15df99db7cf86c31af6b948278ddc6d228ed314aa15b97acb41d9009273 +size 1697 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamRight.uasset new file mode 100644 index 0000000..3435283 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/LaserBeamRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eb9f86043920401304778a1247f7ccf600cfe2b62c8e78d4b5921d7575b5bf2 +size 1706 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_X.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_X.uasset new file mode 100644 index 0000000..76bfaff --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_X.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9514284ca54c4019335be42544a9c29de3e2ea179616f932b575a8ae927b0fd +size 1972 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_Y.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_Y.uasset new file mode 100644 index 0000000..65f005c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbLeft_Y.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dc37821f7f0f5f745b527f6eb5ef09846a144a745ec9e970e4a54fe35a352c8 +size 1972 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_X.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_X.uasset new file mode 100644 index 0000000..7eb201d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_X.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c809975f9f3d5b164dd94b1c5a13359035a3aa55a1201d28ef694eb8dcca694b +size 1981 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_Y.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_Y.uasset new file mode 100644 index 0000000..4fb4d3f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/MotionControllerThumbRight_Y.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08334d0a62c0064bcbe385e62e42f18fd5394b8de87935dbf191c09b37d0703e +size 1981 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripLeft.uasset new file mode 100644 index 0000000..0a2ad1a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2118297fb54c03b90d3f600dc2359ae504078a606720a5b02a4504cff322153a +size 1725 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripRight.uasset new file mode 100644 index 0000000..f78e572 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/PrimaryGripRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45c451a28c509a1f29e80ee9d05df72e40267f376734866cd4c60f6a80ee0e8f +size 1734 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportLeft.uasset new file mode 100644 index 0000000..9177e19 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a5639edfcc13491b91cc89d033361dbb8b92776c278a871d13ae51837ae9c82 +size 1686 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportRight.uasset new file mode 100644 index 0000000..054131e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/TeleportRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bca00b14c3c592ae8c8f97963794904c5718f02c62e59161e845b35305c62547 +size 1695 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectLeft.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectLeft.uasset new file mode 100644 index 0000000..c55059d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de78a3a8cf9cb1ccd1f1a67814856637922e53c76dab8e5675f3f044d985d21d +size 1729 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectRight.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectRight.uasset new file mode 100644 index 0000000..f93d245 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/Actions/UseHeldObjectRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fb7e74eb8d0e577d5dd293a3e23bc7d850f43938a0f97ac16c8ba4cdd751863 +size 1738 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputConfig.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputConfig.uasset new file mode 100644 index 0000000..7320fc5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputConfig.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a3dbd80f436cfb88b32ad754672a97aeb7831d9b27f0afd118dbb2e76a902f4 +size 2238 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputContext.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputContext.uasset new file mode 100644 index 0000000..97f2e4a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/FPSInputContext.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d736f494febc8d56fbc8752af45f9f260547664bbe483db52c40e6960dc817b +size 11478 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputConfig.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputConfig.uasset new file mode 100644 index 0000000..e57a916 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputConfig.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a93b6b73d5169be5db2c22c81bda929bb9a40900382197dac7f10f15ab59b694 +size 2252 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputMappingContext.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputMappingContext.uasset new file mode 100644 index 0000000..c217feb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Input/VREInputMappingContext.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0779907ddaccc85ddc7e510b3daa296045c10d8343c75b727493ce05e15b17bc +size 54466 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/FrontWheel.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/FrontWheel.uasset new file mode 100644 index 0000000..2aaa864 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/FrontWheel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ff5226c6d823f8dabf9a119e500c94c38f73e712ea4f7f7f6afca2ba07937cb +size 6125 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan.uasset new file mode 100644 index 0000000..eac566c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce2cd364db5148595af16df814568e853c4795cdb9a85c73ebf6dcc1d0324c2e +size 80345 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Glass.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Glass.uasset new file mode 100644 index 0000000..516d9bc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Glass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f824bb413380bd1e4fdc134dd483fd4507bb1433e196affe9a8135f1ab42fd0c +size 9253 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Headlights.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Headlights.uasset new file mode 100644 index 0000000..6812515 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Headlights.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca34d3c81f602c0171bc6487ad2ee5d9d61e367d724d5901468a6ef6f68f6df9 +size 9320 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Plastic.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Plastic.uasset new file mode 100644 index 0000000..7b2b6d4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Plastic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:684a9e971512c9cfea6d34958b067e94882547e593da269f763f4af9fdedb8ec +size 7897 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_TailLights.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_TailLights.uasset new file mode 100644 index 0000000..9d7e7a7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_TailLights.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce9249e77130d60f498e04affb1dc188fbd53d51fecc40a7bfc4b3b9c97e24b +size 9352 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Tires.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Tires.uasset new file mode 100644 index 0000000..26ea8c8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Tires.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e457133da6737a691055d244332179173ea12542159c0205154e22471e0f4291 +size 7559 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Wheels.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Wheels.uasset new file mode 100644 index 0000000..643dd84 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Materials/M_Vehicle_Sedan_Inst_Wheels.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f3a4c897de984f16e4e94646a0d0934d5aff79fbc8edf106b1800797afb9043 +size 9385 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/RearWheel.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/RearWheel.uasset new file mode 100644 index 0000000..7e0949a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/RearWheel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb171705409ab03549023f637889281c864af540a4cd2cf9e2bee11e98466391 +size 6057 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_AnimBP.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_AnimBP.uasset new file mode 100644 index 0000000..a77d01d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_AnimBP.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69f58b972446fdac5fa06ba68227f995d22a1a4a6d4235fcfa63b6e7f8fbb868 +size 140414 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysMat.uasset new file mode 100644 index 0000000..5cf59a8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c3d7abb5517c9c5c43f78cc4c4a83762ca33852d92232b62125a8986c54e9bb +size 1310 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysicsAsset.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysicsAsset.uasset new file mode 100644 index 0000000..f2dbc68 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_PhysicsAsset.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c1fd3bf8b4ff23436a824fd2340580e15017dc0c095f1f36907e77ae3b8514 +size 361660 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh.uasset new file mode 100644 index 0000000..3b2a848 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc5f67c9ba2f5295d2018f68b5e5f047db15b0bb3924aa87448bfbbe6ca8067b +size 3665493 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh_Physics.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh_Physics.uasset new file mode 100644 index 0000000..ce2991d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_SkelMesh_Physics.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0999149ddb59656da7236c8ef4fa0d81d3bce047e10574ecd71d752f5a46e07b +size 361365 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_Skeleton.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_Skeleton.uasset new file mode 100644 index 0000000..ee232d9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/Vehicle/Sedan/Sedan_Skeleton.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a5fbcaa9e0b4dfb6f7d4095853718252887d767cdc00fe52b13621374049e70 +size 8338 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/ArenaMesh/LoopBig.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/ArenaMesh/LoopBig.uasset new file mode 100644 index 0000000..190d20a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/ArenaMesh/LoopBig.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbce931b614a6e6ea567c99dfca6c9f8f4a943a35bf03556212baa77ebccab2a +size 45221 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MasterSolidColor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MasterSolidColor.uasset new file mode 100644 index 0000000..ad3870b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MasterSolidColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d9afa56344449a31b7b84692c028610c0fd1ed112063fd6c99acd57fa9843b7 +size 112697 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MaterialInstances/SolidOrange.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MaterialInstances/SolidOrange.uasset new file mode 100644 index 0000000..c6470f8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleAdv/Materials/MaterialInstances/SolidOrange.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb32a9e8a5294f86fea30d174b40f94f73354afe86918c1277f738d9d641306c +size 113444 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/PassengerInfo.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/PassengerInfo.uasset new file mode 100644 index 0000000..6153d28 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/PassengerInfo.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c363ef9c2d2737fae7f94fc100580601f146bb54e81e85f41aa048146609f3a +size 5053 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Sedan.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Sedan.uasset new file mode 100644 index 0000000..e4610db --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Sedan.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:106197b135fa8ffa3a2ce3bf80b112fe6608cc48b815fd0373b3fdd39dd6f823 +size 874870 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/SteeringWheel.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/SteeringWheel.uasset new file mode 100644 index 0000000..d98b232 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/SteeringWheel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:876735fa638611f892f65c692b440f7f73c39a17595a45370c6ebe4f317dc149 +size 30710 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/ThrottleLever.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/ThrottleLever.uasset new file mode 100644 index 0000000..d563275 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/ThrottleLever.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:635cb356d1c5527efce63067594d1b5d3ef86311ca935b3505104c33fe28ca5c +size 47382 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Turquoise.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Turquoise.uasset new file mode 100644 index 0000000..cecc2af --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Turquoise.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f958dc04dfec97c6c276e0758bf14efce612c5f7ea3efc9c86d95456248e2e1 +size 77363 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Yellow.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Yellow.uasset new file mode 100644 index 0000000..698ea36 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Car/VehicleBP/Sedan/Yellow.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56e63af559bfc33c426375085614864c582541a394c3594978fef13dd26cd1dc +size 75748 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/ConsoleOutput.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/ConsoleOutput.uasset new file mode 100644 index 0000000..9cfcadd --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/ConsoleOutput.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8509d96aef98eee98867e8c1e46bf4436d2343600848756448166cc3f2ff8ea2 +size 789315 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/VRConsoleMaterial.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/VRConsoleMaterial.uasset new file mode 100644 index 0000000..0ac27cf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Console/VRConsoleMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0862ca33e347ace106d392902a6329c9c51b6e08176e0485417c7dc0301a6d0 +size 12326 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/2DKeyboard.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/2DKeyboard.uasset new file mode 100644 index 0000000..9879206 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/2DKeyboard.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44661d761195da9d072b67bb113f1838cfd1d098cae2db645e4624ccd4afb4fe +size 321128 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboard.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboard.uasset new file mode 100644 index 0000000..a9af119 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboard.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e22f99d5e56041b779772be7a63ba5dadd4dafd3d601b8769a01b38eadd2da57 +size 407554 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboardKey.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboardKey.uasset new file mode 100644 index 0000000..e45f3e7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/3DKeyboardKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e8c4f29f92e9a26ca55edd6cf2ae5137d0685eaa464b9e80dd4bb0fb8f3b1d8 +size 240589 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/BaseKeyboardKey.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/BaseKeyboardKey.uasset new file mode 100644 index 0000000..6749129 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/BaseKeyboardKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ce1c750fc1377956a31649b38f8eb7f28f312e61dae4d70cc64e94c7403280b +size 86027 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/GreenFaceKeyboardKey.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/GreenFaceKeyboardKey.uasset new file mode 100644 index 0000000..12d6c2d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/GreenFaceKeyboardKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:301c68d773c5dcfe727c53f17bb05e2e2c5023b0998487de2d1744add834ff4c +size 127284 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardDataTable.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardDataTable.uasset new file mode 100644 index 0000000..c15b51b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardDataTable.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5399edf9f2aeaa23649658320ff63462de49dc6c00a915725b136487fc6c7e9 +size 38731 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardKeyTableRow.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardKeyTableRow.uasset new file mode 100644 index 0000000..3031dd7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardKeyTableRow.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e541ec061bc7cd0984ab9b194dab84d030f32f7b2d9e079462ebe9a7643893e +size 10438 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardTestActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardTestActor.uasset new file mode 100644 index 0000000..f65b11c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeyboardTestActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2b243e1cd3c289f7854c4cf467e2955c62caaa01d44881b61e58842417c4e77 +size 76187 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeypadDataTable.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeypadDataTable.uasset new file mode 100644 index 0000000..9596ccb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/KeypadDataTable.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d1ef2e7d8fde8a332cec51c4632c939422f78a28fdf7f06b515279c1b978956 +size 12056 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/SpecialKeyType.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/SpecialKeyType.uasset new file mode 100644 index 0000000..c481253 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/SpecialKeyType.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c07e1ed35deb9ac19ca322384ff3d5a3a4d9cc3432188ae22244584126e1b402 +size 8721 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/TextKeyboardKey.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/TextKeyboardKey.uasset new file mode 100644 index 0000000..34eabb1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Keyboard/TextKeyboardKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26a31e4ac534a8aded5536364121a35b45c62ce2f298ae3dc5b25605f2c2b856 +size 85254 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorActor.uasset new file mode 100644 index 0000000..b0dadf0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30054a1224d11629877aec1b8a038bf079abfc84b9bb5230901e8d48b0f95c27 +size 51272 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorFeed.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorFeed.uasset new file mode 100644 index 0000000..9019636 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorFeed.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3702ec3cb3cbf0a574e3253078c333a04dfdde1609ba81ef9faf6b8f9ab65fff +size 38762 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorMat.uasset new file mode 100644 index 0000000..0393cc4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Mirror/MirrorMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b21a4b79201347ca5135e003c04995f1d1704275ae5ab2f9cb0d0e28e437375 +size 28408 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ClimbWallActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ClimbWallActor.uasset new file mode 100644 index 0000000..e9f878c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ClimbWallActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce2739b0b9d3d45b096bcd07e52493d9f704075337f94d033dd7a0a037193325 +size 41320 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ConsoleCommandSliderActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ConsoleCommandSliderActor.uasset new file mode 100644 index 0000000..97fc719 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/ConsoleCommandSliderActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4a9fe524691dd02d2d6baed4b0c7193cbf24d84812493ddff6621c2cafcd5e3 +size 218357 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Floor_StaticMesh.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Floor_StaticMesh.uasset new file mode 100644 index 0000000..e1316a2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Floor_StaticMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1221e832c3e593122a34f1a2af1323ead3dd2c1c531ea7e76c086f00cba42c4 +size 15023 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Rotator.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Rotator.uasset new file mode 100644 index 0000000..8ef4d2a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/Misc/Rotator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a73a922a3e8c6651a9338c16fb071fab3620521ee934e0d2ee5117d403aa1a1 +size 50905 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenu.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenu.uasset new file mode 100644 index 0000000..f4c1ec0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenu.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:139496f4ef272cb1d13084015f9b96287296a91246acc5cfa3f7b57cc92d0c78 +size 161477 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenuActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenuActor.uasset new file mode 100644 index 0000000..7f052b2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/OptionsMenu/OptionsMenuActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:748e0fcff1e1e348dd08bd825b04f97aab7f3bf7299e2b5a140b55ab4b1e6c51 +size 23156 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenu.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenu.uasset new file mode 100644 index 0000000..7b7bd50 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenu.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e8e33ea948f9b6ef3b99cef1e3e0ce2377d19bce9953abb1094adb51cc9327 +size 276488 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenuActor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenuActor.uasset new file mode 100644 index 0000000..590c8a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/3DMenuActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48fd0a8a60b6dfa0aa072bc57d190f423ce1501139ab5d97e66f44372b96ea2 +size 23395 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/ServerInfo.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/ServerInfo.uasset new file mode 100644 index 0000000..d37c5d7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/ServerMenu/ServerInfo.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:142cb376e836f3b6ebd3da84ca963e2e2400beac1e1532e773b6e23eb5790879 +size 77987 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingMat.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingMat.uasset new file mode 100644 index 0000000..b7ffd3f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b4d1001ff45b75964b0cbe3ae97e457a70e01aa5da965b0abbbe0d3358eb08d +size 8963 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingRamp.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingRamp.uasset new file mode 100644 index 0000000..f3dddf0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingRamp.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:607f2e7b5e5e574c7c9701289fd283466a381affa1bf40c2eea16ce6ff418cfb +size 22517 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingVolume.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingVolume.uasset new file mode 100644 index 0000000..1be9b61 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingVolume.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60b46b6b8e21d6860ba5bedb096c1d2fc608d4529e4739cbba0fb0c4472bdd9a +size 62508 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingWall.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingWall.uasset new file mode 100644 index 0000000..6f7f212 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Level/WallWalking/WallWalkingWall.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1089fee314fe843d0c9edb06f4de9c886147412063dff81505580e012b3d5d8 +size 14964 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube.uasset new file mode 100644 index 0000000..2895a52 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec84195d01bfbac3fc8b0ab497c0c33fc6bae4254b91cec65d8a4e6e1904989 +size 15666 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube_Chamfer.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube_Chamfer.uasset new file mode 100644 index 0000000..5ec7c86 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/1M_Cube_Chamfer.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1126f5d84f073db0a0b171757f6c82d6cc5535e48e762ba9f88328f73168a1bf +size 89045 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/CubeMaterial.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/CubeMaterial.uasset new file mode 100644 index 0000000..67f9fa5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/CubeMaterial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afb6e4332e8e6adbc6371511f9e464e91c9b866368fcb07e076e510da8dd5465 +size 93059 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/TemplateFloor.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/TemplateFloor.uasset new file mode 100644 index 0000000..21c8e48 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/Meshes/TemplateFloor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:deffbbd85fa4b8b5744f2f4dc1db5921e1993716acfcc1c1f18ab37d8a5bb49a +size 95181 diff --git a/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/PostProcess/PP_StereoTexture.uasset b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/PostProcess/PP_StereoTexture.uasset new file mode 100644 index 0000000..a297522 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Content/VRE/Misc/PostProcess/PP_StereoTexture.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eafc13b2fd952c0ee10ea95a98a6e718d3ac9cb25992560a3395ee4f8c812191 +size 79674 diff --git a/VIRTUOS_ExpansionPluginTests/LICENSE.txt b/VIRTUOS_ExpansionPluginTests/LICENSE.txt new file mode 100644 index 0000000..986c999 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright Joshua Statzer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitattributes b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitattributes new file mode 100644 index 0000000..3373152 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.bat eol=crlf \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitignore b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitignore new file mode 100644 index 0000000..3cdb673 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/.gitignore @@ -0,0 +1,10 @@ + +.hg/ +binaries/ +deriveddatacache/ +.vs/ +build/ +intermediate/ +PACKPLUGIN/ +saved/ +*.orig \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/AdvancedSessions.uplugin b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/AdvancedSessions.uplugin new file mode 100644 index 0000000..594ee8f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/AdvancedSessions.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 3, + "FriendlyName": "Advanced Sessions", + "Version": 5.1, + "VersionName": "5.1", + "Description": "Adds new blueprint functions to handle more advanced session operations.", + "Category": "Advanced Sessions Plugin", + "CreatedBy": "Joshua Statzer", + "CreatedByURL": "N/A", + "Modules": [ + { + "Name": "AdvancedSessions", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + } + ], + "Plugins": [ + { + "Name": "OnlineSubsystem", + "Enabled": true + }, + { + "Name": "OnlineSubsystemUtils", + "Enabled": true + } + ], + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Config/FilterPlugin.ini b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[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 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Resources/Icon128.png b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Resources/Icon128.png new file mode 100644 index 0000000..bc2b90d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a4c9fd3d85a99b80d3008e8c9ede55943cc16b79a382a8200b3fa4883d5468b +size 3371 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/AdvancedSessions.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/AdvancedSessions.Build.cs new file mode 100644 index 0000000..a305151 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/AdvancedSessions.Build.cs @@ -0,0 +1,17 @@ +using UnrealBuildTool; +using System.IO; + +public class AdvancedSessions : ModuleRules +{ + public AdvancedSessions(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //bEnforceIWYU = true; + + PublicDefinitions.Add("WITH_ADVANCED_SESSIONS=1"); + + // PrivateIncludePaths.AddRange(new string[] { "AdvancedSessions/Private"/*, "OnlineSubsystemSteam/Private"*/ }); + // PublicIncludePaths.AddRange(new string[] { "AdvancedSessions/Public" }); + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "OnlineSubsystem", "CoreUObject", "OnlineSubsystemUtils", "Networking", "Sockets"/*"Voice", "OnlineSubsystemSteam"*/ }); + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedExternalUILibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedExternalUILibrary.h new file mode 100644 index 0000000..968f623 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedExternalUILibrary.h @@ -0,0 +1,63 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "Engine/LocalPlayer.h" +#include "OnlineSubsystem.h" +#include "BlueprintDataDefinitions.h" +//#include "OnlineFriendsInterface.h" +//#include "OnlineUserInterface.h" +//#include "OnlineMessageInterface.h" +//#include "OnlinePresenceInterface.h" +//#include "Engine/GameInstance.h" +#include "Interfaces/OnlineSessionInterface.h" + +//#include "UObjectIterator.h" + +#include "AdvancedExternalUILibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedExternalUILog, Log, All); + +UCLASS() +class UAdvancedExternalUILibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + //********* External UI Functions *************// + + // Show the UI that handles the Friends list + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result")) + static void ShowFriendsUI(APlayerController *PlayerController, EBlueprintResultSwitch &Result); + + // Show the UI that handles inviting people to your game + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result")) + static void ShowInviteUI(APlayerController *PlayerController, EBlueprintResultSwitch &Result); + + // Show the UI that shows the leaderboard (doesn't work with steam) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result")) + static void ShowLeaderBoardUI(FString LeaderboardName, EBlueprintResultSwitch &Result); + + // Show the UI that shows a web URL + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result", AutoCreateRefTerm = "AllowedDomains")) + static void ShowWebURLUI(FString URLToShow, EBlueprintResultSwitch &Result, TArray& AllowedDomains, bool bEmbedded = false , bool bShowBackground = false, bool bShowCloseButton = false, int32 OffsetX = 0, int32 OffsetY = 0, int32 SizeX = 0, int32 SizeY = 0); + + // Show the UI that shows a web URL + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI") + static void CloseWebURLUI(); + + + // Show the UI that shows the profile of a uniquenetid + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result")) + static void ShowProfileUI(const FBPUniqueNetId PlayerViewingProfile, const FBPUniqueNetId PlayerToViewProfileOf, EBlueprintResultSwitch &Result); + + // Show the UI that shows the account upgrade UI (doesn't work with steam) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedExternalUI", meta = (ExpandEnumAsExecs = "Result")) + static void ShowAccountUpgradeUI(const FBPUniqueNetId PlayerRequestingAccountUpgradeUI, EBlueprintResultSwitch &Result); + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsGameInstance.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsGameInstance.h new file mode 100644 index 0000000..be1def9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsGameInstance.h @@ -0,0 +1,145 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineFriendsInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlineMessageInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Engine/LocalPlayer.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "OnlineSessionSettings.h" +#include "UObject/UObjectIterator.h" +#include "AdvancedFriendsInterface.h" + +#include "AdvancedFriendsGameInstance.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedFriendsInterfaceLog, Log, All); + +UCLASS() +class ADVANCEDSESSIONS_API UAdvancedFriendsGameInstance : public UGameInstance +{ + GENERATED_BODY() +public: + + UAdvancedFriendsGameInstance(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AdvancedFriendsInterface) + bool bCallFriendInterfaceEventsOnPlayerControllers; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AdvancedFriendsInterface) + bool bCallIdentityInterfaceEventsOnPlayerControllers; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AdvancedFriendsInterface) + bool bCallVoiceInterfaceEventsOnPlayerControllers; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AdvancedVoiceInterface) + bool bEnableTalkingStatusDelegate; + + //virtual void PostLoad() override; + virtual void Shutdown() override; + virtual void Init() override; + + //*** Session invite received by local ***// + FOnSessionInviteReceivedDelegate SessionInviteReceivedDelegate; + FDelegateHandle SessionInviteReceivedDelegateHandle; + + //const FUniqueNetId& /*UserId*/, const FUniqueNetId& /*FromId*/, const FString& /*AppId*/, const FOnlineSessionSearchResult& /*InviteResult*/ + void OnSessionInviteReceivedMaster(const FUniqueNetId & PersonInvited, const FUniqueNetId & PersonInviting, const FString & AppId, const FOnlineSessionSearchResult& SessionToJoin); + + // After a session invite has been accepted by the local player this event is triggered, call JoinSession on the session result to join it + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnSessionInviteReceived(int32 LocalPlayerNum, FBPUniqueNetId PersonInviting, const FString& AppId, const FBlueprintSessionResult& SessionToJoin); + + //*** Session invite accepted by local ***// + FOnSessionUserInviteAcceptedDelegate SessionInviteAcceptedDelegate; + FDelegateHandle SessionInviteAcceptedDelegateHandle; + + void OnSessionInviteAcceptedMaster(const bool bWasSuccessful, int32 LocalPlayer, TSharedPtr PersonInviting, const FOnlineSessionSearchResult& SessionToJoin); + + // After a session invite has been accepted by the local player this event is triggered, call JoinSession on the session result to join it + // This function is currently not hooked up in any of Epics default subsystems, it is here for custom subsystems + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnSessionInviteAccepted(int32 LocalPlayerNum, FBPUniqueNetId PersonInvited, const FBlueprintSessionResult& SessionToJoin); + + + // After a voice status has changed this event is triggered if the bEnableTalkingStatusDelegate property is true + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedVoice") + void OnPlayerTalkingStateChanged(FBPUniqueNetId PlayerId, bool bIsTalking); + + void OnPlayerTalkingStateChangedMaster(TSharedRef PlayerId, bool bIsTalking); + + FOnPlayerTalkingStateChangedDelegate PlayerTalkingStateChangedDelegate; + FDelegateHandle PlayerTalkingStateChangedDelegateHandle; + + + // Called when the designated LocalUser has changed login state + UFUNCTION(BlueprintImplementableEvent , Category = "AdvancedIdentity", meta = (DisplayName = "OnPlayerLoginChanged")) + void OnPlayerLoginChanged(int32 PlayerNum); + + void OnPlayerLoginChangedMaster(int32 PlayerNum); + FOnLoginChangedDelegate PlayerLoginChangedDelegate; + FDelegateHandle PlayerLoginChangedDelegateHandle; + + // Called when the designated LocalUser has changed login status + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedIdentity", meta = (DisplayName = "OnPlayerLoginStatusChanged")) + void OnPlayerLoginStatusChanged(int32 PlayerNum, EBPLoginStatus PreviousStatus, EBPLoginStatus NewStatus, FBPUniqueNetId NewPlayerUniqueNetID); + + void OnPlayerLoginStatusChangedMaster(int32 PlayerNum, ELoginStatus::Type PreviousStatus, ELoginStatus::Type NewStatus, const FUniqueNetId & NewPlayerUniqueNetID); + FOnLoginStatusChangedDelegate PlayerLoginStatusChangedDelegate; + FDelegateHandle PlayerLoginStatusChangedDelegateHandle; + + + //*** Session Invite Received From Friend ***// + // REMOVED BECAUSE IT NEVER GETS CALLED + /*FOnSessionInviteReceivedDelegate SessionInviteReceivedDelegate; + FDelegateHandle SessionInviteReceivedDelegateHandle; + + void OnSessionInviteReceivedMaster(const FUniqueNetId &InvitedPlayer, const FUniqueNetId &FriendInviting, const FOnlineSessionSearchResult& Session); + + // After a session invite has been sent from a friend this event is triggered, call JoinSession on the session result to join it + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnSessionInviteReceived(const FBPUniqueNetId &InvitedPlayer, const FBPUniqueNetId &FriendInviting, const FBlueprintSessionResult &Session); + */ + + //*** Friend Invite Accepted ***// + /*FOnInviteAcceptedDelegate FriendInviteAcceptedDelegate; + FDelegateHandle FriendInviteAcceptedDelegateHandle; + + void OnFriendInviteAcceptedDelegateMaster(const FUniqueNetId& LocalPlayer, const FUniqueNetId &PlayerInvited); + + // After a session invite has been accepted by a friend this event is triggered + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnFriendInviteAccepted(const FBPUniqueNetId &InvitedPlayer, const FBPUniqueNetId &PlayerInvited); + */ + + //*** Friend Invite Rejected ***// + /*FOnInviteRejectedDelegate SessionInviteRejectedByFriendDelegate; + FDelegateHandle InviteRejectedByFriendDelegateHandle; + + void OnFriendInviteRejectedDelegateMaster(const FUniqueNetId& LocalPlayer, const FUniqueNetId &PlayerDeclined); + + // After a friend invite has been rejected this event is triggered + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnFriendInviteRejected(const FBPUniqueNetId &InvitedPlayer, const FBPUniqueNetId &PlayerDeclined); + */ + + //*** Removed By Friend ***// + /*FOnFriendRemovedDelegate RemovedByFriendDelegate; + FDelegateHandle RemovedByFriendDelegateHandle; + + void OnRemovedByFriendDelegateMaster(const FUniqueNetId& LocalPlayer, const FUniqueNetId &FriendRemoved); + + // After a friend removed the player this event is triggered + UFUNCTION(BlueprintImplementableEvent, Category = "AdvancedFriends") + void OnRemovedByFriend(const FBPUniqueNetId &InvitedPlayer, const FBPUniqueNetId &FriendRemoved);*/ +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsInterface.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsInterface.h new file mode 100644 index 0000000..44cfc0b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsInterface.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineFriendsInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlineMessageInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "OnlineSessionSettings.h" +#include "UObject/UObjectIterator.h" +#include "BlueprintDataDefinitions.h" +#include "AdvancedFriendsInterface.generated.h" + + +UINTERFACE(MinimalAPI) +class UAdvancedFriendsInterface : public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + +class IAdvancedFriendsInterface +{ + GENERATED_IINTERFACE_BODY() +public: + + // Called when the designated LocalUser has accepted a session invite, use JoinSession on result to connect + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnSessionInviteReceived")) + void OnSessionInviteReceived(FBPUniqueNetId PersonInviting, const FBlueprintSessionResult& SearchResult); + + // Called when the designated LocalUser has accepted a session invite, use JoinSession on result to connect + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnSessionInviteAccepted")) + void OnSessionInviteAccepted(FBPUniqueNetId PersonInvited, const FBlueprintSessionResult& SearchResult); + + // Called when the designated LocalUser has accepted a session invite, use JoinSession on result to connect + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnPlayerVoiceStateChanged")) + void OnPlayerVoiceStateChanged(FBPUniqueNetId PlayerId, bool bIsTalking); + + // Called when the designated LocalUser has changed login state + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnPlayerLoginChanged")) + void OnPlayerLoginChanged(int32 PlayerNum); + + // Called when the designated LocalUser has changed login state + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnPlayerLoginStatusChanged")) + void OnPlayerLoginStatusChanged(EBPLoginStatus PreviousStatus, EBPLoginStatus NewStatus, FBPUniqueNetId PlayerUniqueNetID); + + // REMOVED BECAUSE IT WAS NEVER BEING CALLED + // Called when the designated LocalUser has received a session invite, use JoinSession on result to connect + //UFUNCTION(BlueprintImplementableEvent, meta = (FriendlyName = "OnSessionInviteReceived")) + //void OnSessionInviteReceived(const FBPUniqueNetId &FriendInviting, const FBlueprintSessionResult &Session); + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsLibrary.h new file mode 100644 index 0000000..fedf2c1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedFriendsLibrary.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "Engine/LocalPlayer.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineFriendsInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlineMessageInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Interfaces/OnlineSessionInterface.h" + +#include "UObject/UObjectIterator.h" + +#include "AdvancedFriendsLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedFriendsLog, Log, All); + +UCLASS() +class UAdvancedFriendsLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + //********* Friend List Functions *************// + + // Sends an Invite to the current online session to a list of friends + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|FriendsList", meta = (ExpandEnumAsExecs = "Result")) + static void SendSessionInviteToFriends(APlayerController *PlayerController, const TArray &Friends, EBlueprintResultSwitch &Result); + + // Sends an Invite to the current online session to a friend + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|FriendsList", meta = (ExpandEnumAsExecs = "Result")) + static void SendSessionInviteToFriend(APlayerController *PlayerController, const FBPUniqueNetId &FriendUniqueNetId, EBlueprintResultSwitch &Result); + + // Get a friend from the previously read/saved friends list (Must Call GetFriends first for this to return anything) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|FriendsList") + static void GetFriend(APlayerController *PlayerController, const FBPUniqueNetId FriendUniqueNetId, FBPFriendInfo &Friend); + + // Get the previously read/saved friends list (Must Call GetFriends first for this to return anything) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|FriendsList") + static void GetStoredFriendsList(APlayerController *PlayerController, TArray &FriendsList); + + // Get the previously read/saved recent players list (Must Call GetRecentPlayers first for this to return anything) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|RecentPlayersList") + static void GetStoredRecentPlayersList(FBPUniqueNetId UniqueNetId, TArray &PlayersList); + + // Check if a UniqueNetId is a friend + UFUNCTION(BlueprintPure, Category = "Online|AdvancedFriends|FriendsList") + static void IsAFriend(APlayerController *PlayerController, const FBPUniqueNetId UniqueNetId, bool &IsFriend); +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedGameSession.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedGameSession.h new file mode 100644 index 0000000..179d857 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedGameSession.h @@ -0,0 +1,71 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Engine/GameInstance.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameSession.h" +#include "GameFramework/PlayerState.h" + +//#include "UObjectIterator.h" + +#include "AdvancedGameSession.generated.h" + + + + +/** + A quick wrapper around the game session to add a partial ban implementation. Just bans for the duration of the current session +*/ +UCLASS(config = Game, notplaceable) +class AAdvancedGameSession : public AGameSession +{ + GENERATED_UCLASS_BODY() + +public: + + UPROPERTY(Transient) + TMap BanList; + + virtual bool BanPlayer(class APlayerController* BannedPlayer, const FText& BanReason) + { + + if (APlayerState* PlayerState = (BannedPlayer != NULL) ? BannedPlayer->PlayerState : NULL) + { + FUniqueNetIdRepl UniqueNetID = PlayerState->GetUniqueId(); + bool bWasKicked = KickPlayer(BannedPlayer, BanReason); + + if (bWasKicked) + { + BanList.Add(UniqueNetID, BanReason); + } + + return bWasKicked; + } + + return false; + } + + // This should really be handled in the game mode asking game session, but I didn't want to force a custom game session AND game mode + // If done in the game mode, we could check prior to actually spooling up any player information in ApproveLogin + virtual void PostLogin(APlayerController* NewPlayer) override + { + if (APlayerState* PlayerState = (NewPlayer != NULL) ? NewPlayer->PlayerState : NULL) + { + FUniqueNetIdRepl UniqueNetID = PlayerState->GetUniqueId(); + + if (BanList.Contains(UniqueNetID)) + { + KickPlayer(NewPlayer, BanList[UniqueNetID]); + } + } + } +}; + +AAdvancedGameSession::AAdvancedGameSession(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedIdentityLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedIdentityLibrary.h new file mode 100644 index 0000000..392ab8c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedIdentityLibrary.h @@ -0,0 +1,81 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Engine/LocalPlayer.h" + +#include "UObject/UObjectIterator.h" + +#include "AdvancedIdentityLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedIdentityLog, Log, All); + +UCLASS() +class UAdvancedIdentityLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + //********* Identity Functions *************// + + // Get the login status of a local player + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity", meta = (ExpandEnumAsExecs = "Result")) + static void GetLoginStatus(const FBPUniqueNetId & UniqueNetID, EBPLoginStatus & LoginStatus, EBlueprintResultSwitch &Result); + + // Get the auth token for a local player + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity", meta = (ExpandEnumAsExecs = "Result")) + static void GetPlayerAuthToken(APlayerController * PlayerController, FString & AuthToken, EBlueprintResultSwitch &Result); + + // Get a players nickname + UFUNCTION(BlueprintPure, Category = "Online|AdvancedIdentity") + static void GetPlayerNickname(const FBPUniqueNetId & UniqueNetID, FString & PlayerNickname); + + //********* User Account Functions *************// + + // Get a users account + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity|UserAccount", meta = (ExpandEnumAsExecs = "Result")) + static void GetUserAccount(const FBPUniqueNetId & UniqueNetId, FBPUserOnlineAccount & AccountInfo, EBlueprintResultSwitch &Result); + + // Get all known users accounts + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity|UserAccount", meta = (ExpandEnumAsExecs = "Result")) + static void GetAllUserAccounts(TArray & AccountInfos, EBlueprintResultSwitch &Result); + + // Get a user account access token + UFUNCTION(BlueprintPure, Category = "Online|AdvancedIdentity|UserAccount") + static void GetUserAccountAccessToken(const FBPUserOnlineAccount & AccountInfo, FString & AccessToken); + + // Get a user account Auth attribute (depends on subsystem) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity|UserAccount", meta = (ExpandEnumAsExecs = "Result")) + static void GetUserAccountAuthAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, FString & AuthAttribute, EBlueprintResultSwitch &Result); + + // Set a user account attribute (depends on subsystem) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity|UserAccount", meta = (ExpandEnumAsExecs = "Result")) + static void SetUserAccountAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, const FString & NewAttributeValue, EBlueprintResultSwitch &Result); + + // Get user ID + UFUNCTION(BlueprintPure, Category = "Online|AdvancedIdentity|UserAccount") + static void GetUserID(const FBPUserOnlineAccount & AccountInfo, FBPUniqueNetId & UniqueNetID); + + // Get user accounts real name if possible + UFUNCTION(BlueprintPure, Category = "Online|AdvancedIdentity|UserAccount") + static void GetUserAccountRealName(const FBPUserOnlineAccount & AccountInfo, FString & UserName); + + // Get user account display name if possible + UFUNCTION(BlueprintPure, Category = "Online|AdvancedIdentity|UserAccount") + static void GetUserAccountDisplayName(const FBPUserOnlineAccount & AccountInfo, FString & DisplayName); + + // Get user account attribute (depends on subsystem) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedIdentity|UserAccount", meta = (ExpandEnumAsExecs = "Result")) + static void GetUserAccountAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, FString & AttributeValue, EBlueprintResultSwitch &Result); + + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessions.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessions.h new file mode 100644 index 0000000..6218cd0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessions.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Modules/ModuleManager.h" + +class AdvancedSessions : public IModuleInterface +{ +public: + /** IModuleInterface implementation */ + void StartupModule(); + void ShutdownModule(); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessionsLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessionsLibrary.h new file mode 100644 index 0000000..54153ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedSessionsLibrary.h @@ -0,0 +1,209 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineFriendsInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlineMessageInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Interfaces/OnlineSessionInterface.h" + +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameSession.h" + +//#include "UObjectIterator.h" + +#include "AdvancedSessionsLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedSessionsLog, Log, All); + + +UCLASS() +class UAdvancedSessionsLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + //********* Session Admin Functions *************// + + // Kick a player from the currently active game session, only available on the server + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions", meta = (WorldContext = "WorldContextObject")) + static bool KickPlayer(UObject* WorldContextObject, APlayerController* PlayerToKick, FText KickReason); + + // Ban a player from the currently active game session, only available on the server + // Note that the default gamesession class does not implement an actual ban list and just kicks when this is called + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions", meta = (WorldContext = "WorldContextObject")) + static bool BanPlayer(UObject* WorldContextObject, APlayerController* PlayerToBan, FText BanReason); + + //********* Session Search Functions *************// + + // Adds or modifies session settings in an existing array depending on if they exist already or not + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo") + static void AddOrModifyExtraSettings(UPARAM(ref) TArray & SettingsArray, UPARAM(ref) TArray & NewOrChangedSettings, TArray & ModifiedSettingsArray); + + // Get an array of the session settings from a session search result + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo") + static void GetExtraSettings(FBlueprintSessionResult SessionResult, TArray & ExtraSettings); + + // Get the current session state + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (WorldContext = "WorldContextObject")) + static void GetSessionState(UObject* WorldContextObject, EBPOnlineSessionState &SessionState); + + // Get the current session settings + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "Result", WorldContext = "WorldContextObject")) + static void GetSessionSettings(UObject* WorldContextObject, int32 &NumConnections, int32 &NumPrivateConnections, bool &bIsLAN, bool &bIsDedicated, bool &bAllowInvites, bool &bAllowJoinInProgress, bool &bIsAnticheatEnabled, int32 &BuildUniqueID, TArray &ExtraSettings, EBlueprintResultSwitch &Result); + + // Check if someone is in the current session + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (WorldContext = "WorldContextObject")) + static void IsPlayerInSession(UObject* WorldContextObject, const FBPUniqueNetId &PlayerToCheck, bool &bIsInSession); + + // Make a literal session search parameter + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionsSearchSetting MakeLiteralSessionSearchProperty(FSessionPropertyKeyPair SessionSearchProperty, EOnlineComparisonOpRedux ComparisonOp); + + + //********* Session Information Functions ***********// + + // Check if a session result is valid or not + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo") + static bool IsValidSession(const FBlueprintSessionResult & SessionResult); + + // Get a string copy of a session ID + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo") + static void GetSessionID_AsString(const FBlueprintSessionResult & SessionResult, FString& SessionID); + + // Get a string copy of the current session ID + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo", meta = (WorldContext = "WorldContextObject")) + static void GetCurrentSessionID_AsString(UObject* WorldContextObject, FString& SessionID); + + // Get the Unique Current Build ID + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo") + static void GetCurrentUniqueBuildID(int32 &UniqueBuildId); + + // Get the Unique Build ID from a session search result + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo") + static void GetUniqueBuildID(FBlueprintSessionResult SessionResult, int32 &UniqueBuildId); + + + // Thanks CriErr for submission + + + // Get session property Key Name value + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo") + static FName GetSessionPropertyKey(const FSessionPropertyKeyPair& SessionProperty); + + // Find session property by Name + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "Result")) + static void FindSessionPropertyByName(const TArray& ExtraSettings, FName SettingsName, EBlueprintResultSwitch &Result, FSessionPropertyKeyPair& OutProperty); + + // Find session property index by Name + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "Result")) + static void FindSessionPropertyIndexByName(const TArray& ExtraSettings, FName SettingName, EBlueprintResultSwitch &Result, int32& OutIndex); + + /// Removed the Index_None part of the last function, that isn't accessible in blueprint, better to return success/failure + // End Thanks CriErr :p + + // Get session custom information key/value as Byte (For Enums) + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "SearchResult")) + static void GetSessionPropertyByte(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, uint8 &SettingValue); + + // Get session custom information key/value as Bool + // Steam only currently supports Int,Float,String,BYTE values for search filtering!!! + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "SearchResult")) + static void GetSessionPropertyBool(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, bool &SettingValue); + + // Get session custom information key/value as String + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "SearchResult")) + static void GetSessionPropertyString(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, FString &SettingValue); + + // Get session custom information key/value as Int + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "SearchResult")) + static void GetSessionPropertyInt(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, int32 &SettingValue); + + // Get session custom information key/value as Float + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|SessionInfo", meta = (ExpandEnumAsExecs = "SearchResult")) + static void GetSessionPropertyFloat(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, float &SettingValue); + + + // Make a literal session custom information key/value pair from Byte (For Enums) + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionPropertyKeyPair MakeLiteralSessionPropertyByte(FName Key, uint8 Value); + + // Make a literal session custom information key/value pair from Bool + // Steam only currently supports Int,Float,String,BYTE values for search filtering! + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionPropertyKeyPair MakeLiteralSessionPropertyBool(FName Key, bool Value); + + // Make a literal session custom information key/value pair from String + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionPropertyKeyPair MakeLiteralSessionPropertyString(FName Key, FString Value); + + // Make a literal session custom information key/value pair from Int + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionPropertyKeyPair MakeLiteralSessionPropertyInt(FName Key, int32 Value); + + // Make a literal session custom information key/value pair from Float + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|SessionInfo|Literals") + static FSessionPropertyKeyPair MakeLiteralSessionPropertyFloat(FName Key, float Value); + + + //******* Player ID functions *********// + + // Get the unique net id of a network player attached to the given controller + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|PlayerID") + static void GetUniqueNetID(APlayerController *PlayerController, FBPUniqueNetId &UniqueNetId); + + // Get the unique net id of a network player who is assigned the the given player state + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|PlayerID") + static void GetUniqueNetIDFromPlayerState(APlayerState *PlayerState, FBPUniqueNetId &UniqueNetId); + + // Return True if Unique Net ID is valid + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|PlayerID") + static bool IsValidUniqueNetID(const FBPUniqueNetId &UniqueNetId); + + /* Returns true if the values are equal (A == B) */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equal Unique Net ID", CompactNodeTitle = "==", Keywords = "== equal"), Category = "Online|AdvancedSessions|PlayerInfo|PlayerID") + static bool EqualEqual_UNetIDUnetID(const FBPUniqueNetId &A, const FBPUniqueNetId &B); + + // Check if a UniqueNetId is a friend + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|UniqueNetId") + static void UniqueNetIdToString(const FBPUniqueNetId &UniqueNetId, FString &String); + + //******** Player Name Functions **********// + + // Get the player name of a network player attached to the given controller + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|PlayerName") + static void GetPlayerName(APlayerController *PlayerController, FString &PlayerName); + + // Set the player name of a network player attached to the given controller + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSessions|PlayerInfo|PlayerName") + static void SetPlayerName(APlayerController *PlayerController, FString PlayerName); + + //********** Misc Player Info Functions *********// + + // Get the number of network players + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|Misc", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", DisplayName = "GetNumNetworkPlayers")) + static void GetNumberOfNetworkPlayers(UObject* WorldContextObject, int32 &NumNetPlayers); + + // Get the network player index of the given controller + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|PlayerInfo|Misc") + static void GetNetPlayerIndex(APlayerController *PlayerController, int32 &NetPlayerIndex); + + // Checks if the stated session subsystem is active + UFUNCTION(BlueprintPure, Category = "Online|AdvancedSessions|Misc") + static bool HasOnlineSubsystem(FName SubSystemName); + + //**** Seamless travel Functions ****// + + //Exposes Server travel to blueprint + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Online|AdvancedSessions|Seamless", meta = (HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) + static bool ServerTravel(UObject* WorldContextObject, const FString& InURL, bool bAbsolute, bool bShouldSkipGameNotify); + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedVoiceLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedVoiceLibrary.h new file mode 100644 index 0000000..7f75b6c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AdvancedVoiceLibrary.h @@ -0,0 +1,99 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "BlueprintDataDefinitions.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/VoiceInterface.h" +//#include "OnlineFriendsInterface.h" +//#include "OnlineUserInterface.h" +//#include "OnlineMessageInterface.h" +//#include "OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +//#include "OnlineSessionInterface.h" + +#include "UObject/UObjectIterator.h" + +#include "AdvancedVoiceLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedVoiceLog, Log, All); + + +UCLASS() +class UAdvancedVoiceLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + //********* Voice Library Functions *************// + + // Get if a headset is present for the specified local user + UFUNCTION(BlueprintPure, Category = "Online|AdvancedVoice|VoiceInfo") + static void IsHeadsetPresent(bool & bHasHeadset, uint8 LocalPlayerNum = 0); + + // Starts networked voice, allows push to talk in coordination with StopNetworkedVoice + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void StartNetworkedVoice(uint8 LocalPlayerNum = 0); + + // Stops networked voice, allows push to talk in coordination with StartNetworkedVoice + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void StopNetworkedVoice(uint8 LocalPlayerNum = 0); + + // Registers a local player as someone interested in voice data + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static bool RegisterLocalTalker(uint8 LocalPlayerNum = 0); + + // Registers all signed in players as local talkers + // This is already done automatically, only do it manually if you unregistered someone + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void RegisterAllLocalTalkers(); + + // UnRegisters local player as a local talker + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void UnRegisterLocalTalker(uint8 LocalPlayerNum = 0); + + // UnRegisters all signed in players as local talkers + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void UnRegisterAllLocalTalkers(); + + // Registers a remote player as a talker + // This is already done automatically, only do it manually if you unregistered someone + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static bool RegisterRemoteTalker(const FBPUniqueNetId& UniqueNetId); + + // UnRegisters a remote player as a talker + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static bool UnRegisterRemoteTalker(const FBPUniqueNetId& UniqueNetId); + + // UnRegisters all remote players as talkers + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static void RemoveAllRemoteTalkers(); + + // Returns whether a local player is currently talking + UFUNCTION(BlueprintPure, Category = "Online|AdvancedVoice|VoiceInfo") + static bool IsLocalPlayerTalking(uint8 LocalPlayerNum); + + // Returns whether a remote player is currently talking + UFUNCTION(BlueprintPure, Category = "Online|AdvancedVoice|VoiceInfo") + static bool IsRemotePlayerTalking(const FBPUniqueNetId& UniqueNetId); + + // Returns whether a player is muted for the specified local player + UFUNCTION(BlueprintPure, Category = "Online|AdvancedVoice|VoiceInfo") + static bool IsPlayerMuted(uint8 LocalUserNumChecking, const FBPUniqueNetId& UniqueNetId); + + // Mutes the player associated with the uniquenetid for the specified local player, if IsSystemWide is true then it will attempt to mute globally for the player + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static bool MuteRemoteTalker(uint8 LocalUserNum, const FBPUniqueNetId& UniqueNetId, bool bIsSystemWide = false); + + // UnMutes the player associated with the uniquenetid for the specified local player, if IsSystemWide is true then it will attempt to unmute globally for the player + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedVoice") + static bool UnMuteRemoteTalker(uint8 LocalUserNum, const FBPUniqueNetId& UniqueNetId, bool bIsSystemWide = false); + + // Gets the number of local talkers for this system + UFUNCTION(BlueprintPure, Category = "Online|AdvancedVoice|VoiceInfo") + static void GetNumLocalTalkers(int32 & NumLocalTalkers); +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AutoLoginUserCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AutoLoginUserCallbackProxy.h new file mode 100644 index 0000000..dc91cff --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/AutoLoginUserCallbackProxy.h @@ -0,0 +1,55 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "Engine/LocalPlayer.h" +#include "AutoLoginUserCallbackProxy.generated.h" + +UCLASS(MinimalAPI) +class UAutoLoginUserCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + /** + * Logs the player into the online service using parameters passed on the + * command line. Expects -AUTH_LOGIN= -AUTH_PASSWORD=. If either + * are missing, the function returns false and doesn't start the login + * process + * + * @param LocalUserNum the controller number of the associated user + * + */ + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedIdentity") + static UAutoLoginUserCallbackProxy* AutoLoginUser(UObject* WorldContextObject, int32 LocalUserNum); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& ErrorVal); + +private: + // The controller number of the associated user + int32 LocalUserNumber; + + // The delegate executed by the online subsystem + FOnLoginCompleteDelegate Delegate; + + // Handle to the registered OnDestroySessionComplete delegate + FDelegateHandle DelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/BlueprintDataDefinitions.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/BlueprintDataDefinitions.h new file mode 100644 index 0000000..b5dc438 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/BlueprintDataDefinitions.h @@ -0,0 +1,435 @@ +#pragma once +#include "CoreMinimal.h" +//#include "EngineMinimal.h" +#include "Engine/Engine.h" +#include "GameFramework/PlayerState.h" +//#include "Core.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "OnlineSessionSettings.h" +#include "OnlineDelegateMacros.h" +#include "OnlineSubsystem.h" +#include "OnlineSubsystemImpl.h" +#include "OnlineSubsystemUtils.h" +#include "OnlineSubsystemUtilsModule.h" +#include "GameFramework/PlayerController.h" +#include "Modules/ModuleManager.h" +#include "OnlineSubsystemUtilsClasses.h" +#include "BlueprintDataDefinitions.generated.h" + +UENUM(BlueprintType) +enum class EBPUserPrivileges : uint8 +{ + /** Whether the user can play at all, online or offline - may be age restricted */ + CanPlay, + /** Whether the user can play in online modes */ + CanPlayOnline, + /** Whether the user can use voice and text chat */ + CanCommunicateOnline, + /** Whether the user can use content generated by other users */ + CanUseUserGeneratedContent +}; + + +UENUM(BlueprintType) +enum class EBPLoginStatus : uint8 +{ + /** Player has not logged in or chosen a local profile */ + NotLoggedIn, + /** Player is using a local profile but is not logged in */ + UsingLocalProfile, + /** Player has been validated by the platform specific authentication service */ + LoggedIn +}; + + +USTRUCT(BlueprintType) +struct FBPUserOnlineAccount +{ + GENERATED_USTRUCT_BODY() + +public: + TSharedPtr UserAccountInfo; + + FBPUserOnlineAccount() + { + + } + + FBPUserOnlineAccount(TSharedPtr UserAccount) + { + UserAccountInfo = UserAccount; + } +}; + +UENUM() +enum class ESessionSettingSearchResult : uint8 +{ + // Found the setting + Found, + + // Did not find the setting + NotFound, + + // Was not the correct type + WrongType +}; + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBlueprintResultSwitch : uint8 +{ + // On Success + OnSuccess, + + // On Failure + OnFailure +}; + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBlueprintAsyncResultSwitch : uint8 +{ + // On Success + OnSuccess, + + // Still loading + AsyncLoading, + // On Failure + OnFailure +}; + +// This is to define server type searches +UENUM(BlueprintType) +enum class EBPServerPresenceSearchType : uint8 +{ + AllServers, + ClientServersOnly, + DedicatedServersOnly +}; + +// Wanted this to be switchable in the editor +UENUM(BlueprintType) +enum class EBPOnlinePresenceState : uint8 +{ + Online, + Offline, + Away, + ExtendedAway, + DoNotDisturb, + Chat +}; + +UENUM(BlueprintType) +enum class EBPOnlineSessionState : uint8 +{ + /** An online session has not been created yet */ + NoSession, + /** An online session is in the process of being created */ + Creating, + /** Session has been created but the session hasn't started (pre match lobby) */ + Pending, + /** Session has been asked to start (may take time due to communication with backend) */ + Starting, + /** The current session has started. Sessions with join in progress disabled are no longer joinable */ + InProgress, + /** The session is still valid, but the session is no longer being played (post match lobby) */ + Ending, + /** The session is closed and any stats committed */ + Ended, + /** The session is being destroyed */ + Destroying +}; + +// Boy oh boy is this a dirty hack, but I can't figure out a good way to do it otherwise at the moment +// The UniqueNetId is an abstract class so I can't exactly re-initialize it to make a shared pointer on some functions +// So I made the blueprintable UniqueNetID into a dual variable struct with access functions and I am converting the const var for the pointer +// I really need to re-think this later +USTRUCT(BlueprintType) +struct FBPUniqueNetId +{ + GENERATED_USTRUCT_BODY() + +private: + bool bUseDirectPointer; + + +public: + TSharedPtr UniqueNetId; + const FUniqueNetId * UniqueNetIdPtr; + + void SetUniqueNetId(const TSharedPtr &ID) + { + bUseDirectPointer = false; + UniqueNetIdPtr = nullptr; + UniqueNetId = ID; + } + + void SetUniqueNetId(const FUniqueNetId *ID) + { + bUseDirectPointer = true; + UniqueNetIdPtr = ID; + } + + bool IsValid() const + { + if (bUseDirectPointer && UniqueNetIdPtr != nullptr && UniqueNetIdPtr->IsValid()) + { + return true; + } + else if (UniqueNetId.IsValid()) + { + return true; + } + else + return false; + + } + + const FUniqueNetId* GetUniqueNetId() const + { + if (bUseDirectPointer && UniqueNetIdPtr != nullptr) + { + // No longer converting to non const as all functions now pass const UniqueNetIds + return /*const_cast*/(UniqueNetIdPtr); + } + else if (UniqueNetId.IsValid()) + { + return UniqueNetId.Get(); + } + else + return nullptr; + } + + // Adding in a compare operator so that std functions will work with this struct + FORCEINLINE bool operator==(const FBPUniqueNetId& Other) const + { + return (IsValid() && Other.IsValid() && (*GetUniqueNetId() == *Other.GetUniqueNetId())); + } + + FORCEINLINE bool operator!=(const FBPUniqueNetId& Other) const + { + return !(IsValid() && Other.IsValid() && (*GetUniqueNetId() == *Other.GetUniqueNetId())); + } + + FBPUniqueNetId() + { + bUseDirectPointer = false; + UniqueNetIdPtr = nullptr; + } +}; + +USTRUCT(BluePrintType) +struct FBPOnlineUser +{ + GENERATED_USTRUCT_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FBPUniqueNetId UniqueNetId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString DisplayName; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString RealName; +}; + +USTRUCT(BluePrintType) +struct FBPOnlineRecentPlayer : public FBPOnlineUser +{ + GENERATED_USTRUCT_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString LastSeen; +}; + + +USTRUCT(BlueprintType) +struct FBPFriendPresenceInfo +{ + GENERATED_USTRUCT_BODY() + +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bIsOnline = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bIsPlaying = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bIsPlayingThisGame = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bIsJoinable = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bHasVoiceSupport = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + EBPOnlinePresenceState PresenceState = EBPOnlinePresenceState::Offline; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString StatusString; + + FBPFriendPresenceInfo() + { + bIsOnline = false; + bIsPlaying = false; + bIsPlayingThisGame = false; + bIsJoinable = false; + bHasVoiceSupport = false; + PresenceState = EBPOnlinePresenceState::Offline; + } +}; + +USTRUCT(BlueprintType) +struct FBPFriendInfo +{ + GENERATED_USTRUCT_BODY() + +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString DisplayName; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FString RealName; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + EBPOnlinePresenceState OnlineState = EBPOnlinePresenceState::Offline; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FBPUniqueNetId UniqueNetId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + bool bIsPlayingSameGame = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Online|Friend") + FBPFriendPresenceInfo PresenceInfo; + + FBPFriendInfo() + { + OnlineState = EBPOnlinePresenceState::Offline; + bIsPlayingSameGame = false; + } +}; + + +/** The types of comparison operations for a given search query */ +// Used to compare session properties +UENUM(BlueprintType) +enum class EOnlineComparisonOpRedux : uint8 +{ + Equals, + NotEquals, + GreaterThan, + GreaterThanEquals, + LessThan, + LessThanEquals, +}; + + +// Used to store session properties before converting to FVariantData +USTRUCT(BlueprintType) +struct FSessionPropertyKeyPair +{ + GENERATED_USTRUCT_BODY() + + FName Key; + FVariantData Data; +}; + + +// Sent to the FindSessionsAdvanced to filter the end results +USTRUCT(BlueprintType) +struct FSessionsSearchSetting +{ + GENERATED_USTRUCT_BODY() + //UPROPERTY() + + + // Had to make a copy of this to account for the original not being exposed to blueprints + /** How is this session setting compared on the backend searches */ + EOnlineComparisonOpRedux ComparisonOp; + + // The key pair to search for + FSessionPropertyKeyPair PropertyKeyPair; +}; + +// Couldn't use the default one as it is not exposed to other modules, had to re-create it here +// Helper class for various methods to reduce the call hierarchy +struct FOnlineSubsystemBPCallHelperAdvanced +{ +public: + FOnlineSubsystemBPCallHelperAdvanced(const TCHAR* CallFunctionContext, UWorld* World, FName SystemName = NAME_None) + : OnlineSub(Online::GetSubsystem(World, SystemName)) + , FunctionContext(CallFunctionContext) + { + if (OnlineSub == nullptr) + { + FFrame::KismetExecutionMessage(*FString::Printf(TEXT("%s - Invalid or uninitialized OnlineSubsystem"), FunctionContext), ELogVerbosity::Warning); + } + } + + void QueryIDFromPlayerController(APlayerController* PlayerController) + { + UserID.Reset(); + //return const_cast(UniqueNetIdPtr); + if (APlayerState* PlayerState = (PlayerController != NULL) ? PlayerController->PlayerState : NULL) + { + UserID = PlayerState->GetUniqueId().GetUniqueNetId(); + if (!UserID.IsValid()) + { + FFrame::KismetExecutionMessage(*FString::Printf(TEXT("%s - Cannot map local player to unique net ID"), FunctionContext), ELogVerbosity::Warning); + } + } + else + { + FFrame::KismetExecutionMessage(*FString::Printf(TEXT("%s - Invalid player state"), FunctionContext), ELogVerbosity::Warning); + } + } + + + bool IsValid() const + { + return UserID.IsValid() && (OnlineSub != nullptr); + } + +public: + //TSharedPtr& GetUniqueNetId() + TSharedPtr UserID; + IOnlineSubsystem* const OnlineSub; + const TCHAR* FunctionContext; +}; +class FOnlineSearchSettingsEx : public FOnlineSearchSettings +{ + /** + * Sets a key value pair combination that defines a search parameter + * + * @param Key key for the setting + * @param Value value of the setting + * @param InType type of comparison + */ +public: + + void HardSet(FName Key, const FVariantData& Value, EOnlineComparisonOpRedux CompOp) + { + FOnlineSessionSearchParam* SearchParam = SearchParams.Find(Key); + + TEnumAsByte op; + + switch (CompOp) + { + case EOnlineComparisonOpRedux::Equals: op = EOnlineComparisonOp::Equals; break; + case EOnlineComparisonOpRedux::GreaterThan: op = EOnlineComparisonOp::GreaterThan; break; + case EOnlineComparisonOpRedux::GreaterThanEquals: op = EOnlineComparisonOp::GreaterThanEquals; break; + case EOnlineComparisonOpRedux::LessThan: op = EOnlineComparisonOp::LessThan; break; + case EOnlineComparisonOpRedux::LessThanEquals: op = EOnlineComparisonOp::LessThanEquals; break; + case EOnlineComparisonOpRedux::NotEquals: op = EOnlineComparisonOp::NotEquals; break; + default: op = EOnlineComparisonOp::Equals; break; + } + + if (SearchParam) + { + SearchParam->Data = Value; + SearchParam->ComparisonOp = op; + } + else + { + FOnlineSessionSearchParam searchSetting((int)0, op); + searchSetting.Data = Value; + SearchParams.Add(Key, searchSetting); + } + } +}; + +#define INVALID_INDEX -1 \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CancelFindSessionsCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CancelFindSessionsCallbackProxy.h new file mode 100644 index 0000000..59ebff3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CancelFindSessionsCallbackProxy.h @@ -0,0 +1,46 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "BlueprintDataDefinitions.h" +#include "CancelFindSessionsCallbackProxy.generated.h" + +UCLASS(MinimalAPI) +class UCancelFindSessionsCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + // Cancels finding sessions + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedSessions") + static UCancelFindSessionsCallbackProxy* CancelFindSessions(UObject* WorldContextObject, class APlayerController* PlayerController); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(bool bWasSuccessful); + +private: + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed by the online subsystem + FOnCancelFindSessionsCompleteDelegate Delegate; + + // Handle to the registered OnDestroySessionComplete delegate + FDelegateHandle DelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CreateSessionCallbackProxyAdvanced.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CreateSessionCallbackProxyAdvanced.h new file mode 100644 index 0000000..013ff0b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/CreateSessionCallbackProxyAdvanced.h @@ -0,0 +1,107 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "BlueprintDataDefinitions.h" +#include "CreateSessionCallbackProxyAdvanced.generated.h" + +UCLASS(MinimalAPI) +class UCreateSessionCallbackProxyAdvanced : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the session was created successfully + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there was an error creating the session + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + /** + * Creates a session with the default online subsystem with advanced optional inputs, for dedicated servers leave UsePresence as false and set IsDedicatedServer to true. Dedicated servers don't use presence. + * @param PublicConnections When doing a 'listen' server, this must be >=2 (ListenServer itself counts as a connection) + * @param bUseLAN When you want to play LAN, the level to play on must be loaded with option 'bIsLanMatch' + * @param bUsePresence Must be true for a 'listen' server (Map must be loaded with option 'listen'), false for a 'dedicated' server. + * @param bUseLobbiesIfAvailable Used to flag the subsystem to use a lobby api instead of general hosting if the API supports it, generally true on steam for listen servers and false for dedicated + * @param bShouldAdvertise Set to true when the OnlineSubsystem should list your server when someone is searching for servers. Otherwise the server is hidden and only join via invite is possible. + * @param bUseLobbiesVoiceChatIfAvailable Set to true to setup voice chat lobbies if the API supports it + * @param bStartAfterCreate Set to true to start the session after it's created. If false you need to manually call StartSession when ready. + */ + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject",AutoCreateRefTerm="ExtraSettings"), Category = "Online|AdvancedSessions") + static UCreateSessionCallbackProxyAdvanced* CreateAdvancedSession(UObject* WorldContextObject, const TArray& ExtraSettings, class APlayerController* PlayerController = NULL, int32 PublicConnections = 100, int32 PrivateConnections = 0, bool bUseLAN = false, bool bAllowInvites = true, bool bIsDedicatedServer = false, bool bUsePresence = true, bool bUseLobbiesIfAvailable = true, bool bAllowJoinViaPresence = true, bool bAllowJoinViaPresenceFriendsOnly = false, bool bAntiCheatProtected = false, bool bUsesStats = false, bool bShouldAdvertise = true, bool bUseLobbiesVoiceChatIfAvailable = false, bool bStartAfterCreate = true); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when session creation completes, optionally calls StartSession + void OnCreateCompleted(FName SessionName, bool bWasSuccessful); + + // Internal callback when session start completes + void OnStartCompleted(FName SessionName, bool bWasSuccessful); + + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed by the online subsystem + FOnCreateSessionCompleteDelegate CreateCompleteDelegate; + + // The delegate executed by the online subsystem + FOnStartSessionCompleteDelegate StartCompleteDelegate; + + // Handles to the registered delegates above + FDelegateHandle CreateCompleteDelegateHandle; + FDelegateHandle StartCompleteDelegateHandle; + + // Number of public connections + int NumPublicConnections; + + // Number of private connections + int NumPrivateConnections; + + // Whether or not to search LAN + bool bUseLAN; + + // Whether or not to allow invites + bool bAllowInvites; + + // Whether this is a dedicated server or not + bool bDedicatedServer; + + // Whether to use the presence option + bool bUsePresence; + + // Whether to prefer the use of lobbies for hosting if the api supports them + bool bUseLobbiesIfAvailable; + + // Whether to allow joining via presence + bool bAllowJoinViaPresence; + + // Allow joining via presence for friends only + bool bAllowJoinViaPresenceFriendsOnly; + + // Delcare the server to be anti cheat protected + bool bAntiCheatProtected; + + // Record Stats + bool bUsesStats; + + // Should advertise server? + bool bShouldAdvertise; + + // Whether to prefer the use of voice chat lobbies if the api supports them + bool bUseLobbiesVoiceChatIfAvailable; + + // Whether to start the session automatically after it is created + bool bStartAfterCreate; + + // Store extra settings + TArray ExtraSettings; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/EndSessionCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/EndSessionCallbackProxy.h new file mode 100644 index 0000000..756c645 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/EndSessionCallbackProxy.h @@ -0,0 +1,49 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "BlueprintDataDefinitions.h" +#include "EndSessionCallbackProxy.generated.h" + +UCLASS(MinimalAPI) +class UEndSessionCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + /** + * Ends the current sessions, Generally for almost all uses you should be using the engines native Destroy Session node instead. + * This exists for people using StartSession and optionally hand managing the session state. + */ + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedSessions") + static UEndSessionCallbackProxy* EndSession(UObject* WorldContextObject, class APlayerController* PlayerController); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(FName SessionName, bool bWasSuccessful); + +private: + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed by the online subsystem + FOnEndSessionCompleteDelegate Delegate; + + // Handle to the registered OnDestroySessionComplete delegate + FDelegateHandle DelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindFriendSessionCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindFriendSessionCallbackProxy.h new file mode 100644 index 0000000..6876995 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindFriendSessionCallbackProxy.h @@ -0,0 +1,51 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Engine/LocalPlayer.h" +#include "FindFriendSessionCallbackProxy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(AdvancedFindFriendSessionLog, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBlueprintFindFriendSessionDelegate, const TArray &, SessionInfo); + +UCLASS(MinimalAPI) +class UFindFriendSessionCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the friends list successfully was retrieved + UPROPERTY(BlueprintAssignable) + FBlueprintFindFriendSessionDelegate OnSuccess; + + // Called when there was an error retrieving the friends list + UPROPERTY(BlueprintAssignable) + FBlueprintFindFriendSessionDelegate OnFailure; + + // Attempts to get the current session that a friend is in + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedFriends") + static UFindFriendSessionCallbackProxy* FindFriendSession(UObject* WorldContextObject, APlayerController *PlayerController, const FBPUniqueNetId &FriendUniqueNetId); + + virtual void Activate() override; + +private: + // Internal callback when the friends list is retrieved + void OnFindFriendSessionCompleted(int32 LocalPlayer, bool bWasSuccessful, const TArray& SessionInfo); + + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The UniqueNetID of the person to invite + FBPUniqueNetId cUniqueNetId; + + // The delegate to call on completion + FOnFindFriendSessionCompleteDelegate OnFindFriendSessionCompleteDelegate; + + // Handles to the registered delegates above + FDelegateHandle FindFriendSessionCompleteDelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindSessionsCallbackProxyAdvanced.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindSessionsCallbackProxyAdvanced.h new file mode 100644 index 0000000..a998ba3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/FindSessionsCallbackProxyAdvanced.h @@ -0,0 +1,109 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "FindSessionsCallbackProxy.h" +#include "BlueprintDataDefinitions.h" +#include "FindSessionsCallbackProxyAdvanced.generated.h" + + +FORCEINLINE bool operator==(const FBlueprintSessionResult& A, const FBlueprintSessionResult& B) +{ + return (A.OnlineResult.IsValid() == B.OnlineResult.IsValid() && (A.OnlineResult.GetSessionIdStr() == B.OnlineResult.GetSessionIdStr())); +} + +UCLASS(MinimalAPI) +class UFindSessionsCallbackProxyAdvanced : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful query + UPROPERTY(BlueprintAssignable) + FBlueprintFindSessionsResultDelegate OnSuccess; + + // Called when there is an unsuccessful query + UPROPERTY(BlueprintAssignable) + FBlueprintFindSessionsResultDelegate OnFailure; + + // Searches for advertised sessions with the default online subsystem and includes an array of filters + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", AutoCreateRefTerm="Filters"), Category = "Online|AdvancedSessions") + static UFindSessionsCallbackProxyAdvanced* FindSessionsAdvanced(UObject* WorldContextObject, class APlayerController* PlayerController, int32 MaxResults, bool bUseLAN, EBPServerPresenceSearchType ServerTypeToSearch, const TArray &Filters, bool bEmptyServersOnly = false, bool bNonEmptyServersOnly = false, bool bSecureServersOnly = false, bool bSearchLobbies = true, int MinSlotsAvailable = 0); + + static bool CompareVariants(const FVariantData &A, const FVariantData &B, EOnlineComparisonOpRedux Comparator); + + // Filters an array of session results by the given search parameters, returns a new array with the filtered results + UFUNCTION(BluePrintCallable, meta = (Category = "Online|AdvancedSessions")) + static void FilterSessionResults(const TArray &SessionResults, const TArray &Filters, TArray &FilteredResults); + + // Removed, the default built in versions work fine in the normal FindSessionsCallbackProxy + /*UFUNCTION(BlueprintPure, Category = "Online|Session") + static int32 GetPingInMs(const FBlueprintSessionResult& Result); + + UFUNCTION(BlueprintPure, Category = "Online|Session") + static FString GetServerName(const FBlueprintSessionResult& Result); + + UFUNCTION(BlueprintPure, Category = "Online|Session") + static int32 GetCurrentPlayers(const FBlueprintSessionResult& Result); + + UFUNCTION(BlueprintPure, Category = "Online|Session") + static int32 GetMaxPlayers(const FBlueprintSessionResult& Result);*/ + + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the session search completes, calls out to the public success/failure callbacks + void OnCompleted(bool bSuccess); + + bool bRunSecondSearch; + bool bIsOnSecondSearch; + + TArray SessionSearchResults; + +private: + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed by the online subsystem + FOnFindSessionsCompleteDelegate Delegate; + + // Handle to the registered OnFindSessionsComplete delegate + FDelegateHandle DelegateHandle; + + // Object to track search results + TSharedPtr SearchObject; + TSharedPtr SearchObjectDedicated; + + // Whether or not to search LAN + bool bUseLAN; + + // Whether or not to search for dedicated servers + EBPServerPresenceSearchType ServerSearchType; + + // Maximum number of results to return + int MaxResults; + + // Store extra settings + TArray SearchSettings; + + // Search for empty servers only + bool bEmptyServersOnly; + + // Search for non empty servers only + bool bNonEmptyServersOnly; + + // Search for secure servers only + bool bSecureServersOnly; + + // Search through lobbies + bool bSearchLobbies; + + // Min slots requires to search + int MinSlotsAvailable; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetFriendsCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetFriendsCallbackProxy.h new file mode 100644 index 0000000..57be8f7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetFriendsCallbackProxy.h @@ -0,0 +1,49 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Engine/LocalPlayer.h" +#include "GetFriendsCallbackProxy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(AdvancedGetFriendsLog, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBlueprintGetFriendsListDelegate, const TArray&, Results); + +UCLASS(MinimalAPI) +class UGetFriendsCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the friends list successfully was retrieved + UPROPERTY(BlueprintAssignable) + FBlueprintGetFriendsListDelegate OnSuccess; + + // Called when there was an error retrieving the friends list + UPROPERTY(BlueprintAssignable) + FBlueprintGetFriendsListDelegate OnFailure; + + // Gets the players list of friends from the OnlineSubsystem and returns it, can be retrieved later with GetStoredFriendsList + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedFriends") + static UGetFriendsCallbackProxy* GetAndStoreFriendsList(UObject* WorldContextObject, class APlayerController* PlayerController); + + virtual void Activate() override; + +private: + // Internal callback when the friends list is retrieved + void OnReadFriendsListCompleted(int32 LocalUserNum, bool bWasSuccessful, const FString& ListName, const FString& ErrorString); + + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed + FOnReadFriendsListComplete FriendListReadCompleteDelegate; + + // The Type of friends list to get + // Removed because all but the facebook interfaces don't even currently support anything but the default friends list. + //EBPFriendsLists FriendListToGet; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetRecentPlayersCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetRecentPlayersCallbackProxy.h new file mode 100644 index 0000000..d0d7af4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetRecentPlayersCallbackProxy.h @@ -0,0 +1,49 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "GetRecentPlayersCallbackProxy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(AdvancedGetRecentPlayersLog, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBlueprintGetRecentPlayersDelegate, const TArray&, Results); + +UCLASS(MinimalAPI) +class UGetRecentPlayersCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the friends list successfully was retrieved + UPROPERTY(BlueprintAssignable) + FBlueprintGetRecentPlayersDelegate OnSuccess; + + // Called when there was an error retrieving the friends list + UPROPERTY(BlueprintAssignable) + FBlueprintGetRecentPlayersDelegate OnFailure; + + // Gets the list of recent players from the OnlineSubsystem and returns it, can be retrieved later with GetStoredRecentPlayersList, can fail if no recent players are found + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedFriends") + static UGetRecentPlayersCallbackProxy* GetAndStoreRecentPlayersList(UObject* WorldContextObject, const FBPUniqueNetId &UniqueNetId); + + virtual void Activate() override; + +private: + // Internal callback when the friends list is retrieved + void OnQueryRecentPlayersCompleted(const FUniqueNetId &UserID, const FString &Namespace, bool bWasSuccessful, const FString& ErrorString); + // Handle to the registered OnFindSessionsComplete delegate + FDelegateHandle DelegateHandle; + + // The player controller triggering things + //TWeakObjectPtr PlayerControllerWeakPtr; + + // The UniqueNetID of the person to get recent players for + FBPUniqueNetId cUniqueNetId; + + // The delegate executed + FOnQueryRecentPlayersCompleteDelegate QueryRecentPlayersCompleteDelegate; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetUserPrivilegeCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetUserPrivilegeCallbackProxy.h new file mode 100644 index 0000000..448ab59 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/GetUserPrivilegeCallbackProxy.h @@ -0,0 +1,45 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "GetUserPrivilegeCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FBlueprintGetUserPrivilegeDelegate,/* const &FBPUniqueNetId, PlayerID,*/ EBPUserPrivileges, QueriedPrivilege, bool, HadPrivilege); + +UCLASS(MinimalAPI) +class UGetUserPrivilegeCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FBlueprintGetUserPrivilegeDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + // Gets the privilage of the user + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedIdentity") + static UGetUserPrivilegeCallbackProxy* GetUserPrivilege(UObject* WorldContextObject, const EBPUserPrivileges & PrivilegeToCheck, const FBPUniqueNetId & PlayerUniqueNetID); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(const FUniqueNetId& PlayerID, EUserPrivileges::Type Privilege, uint32 Result); + +private: + // The player controller triggering things + FBPUniqueNetId PlayerUniqueNetID; + + // Privilege to check + EBPUserPrivileges UserPrivilege; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LoginUserCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LoginUserCallbackProxy.h new file mode 100644 index 0000000..08bfd4c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LoginUserCallbackProxy.h @@ -0,0 +1,55 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "Engine/LocalPlayer.h" +#include "LoginUserCallbackProxy.generated.h" + +UCLASS(MinimalAPI) +class ULoginUserCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + // Logs into the identity interface + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject", AdvancedDisplay = "AuthType"), Category = "Online|AdvancedIdentity") + static ULoginUserCallbackProxy* LoginUser(UObject* WorldContextObject, class APlayerController* PlayerController, FString UserID, FString UserToken, FString AuthType); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& ErrorVal); + +private: + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The user ID + FString UserID; + + // The user pass / token + FString UserToken; + + FString AuthType; + + // The delegate executed by the online subsystem + FOnLoginCompleteDelegate Delegate; + + // Handle to the registered OnDestroySessionComplete delegate + FDelegateHandle DelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LogoutUserCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LogoutUserCallbackProxy.h new file mode 100644 index 0000000..9907095 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/LogoutUserCallbackProxy.h @@ -0,0 +1,47 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "Engine/LocalPlayer.h" +#include "LogoutUserCallbackProxy.generated.h" + +UCLASS(MinimalAPI) +class ULogoutUserCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an unsuccessful destroy + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + // Logs out of the identity interface + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedIdentity") + static ULogoutUserCallbackProxy* LogoutUser(UObject* WorldContextObject, class APlayerController* PlayerController); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when the operation completes, calls out to the public success/failure callbacks + void OnCompleted(int LocalUserNum, bool bWasSuccessful); + +private: + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The delegate executed by the online subsystem + FOnLogoutCompleteDelegate Delegate; + + // Handle to the registered OnDestroySessionComplete delegate + FDelegateHandle DelegateHandle; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/OnlineSubSystemHeader.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/OnlineSubSystemHeader.h new file mode 100644 index 0000000..ad4b18f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/OnlineSubSystemHeader.h @@ -0,0 +1,27 @@ +#pragma once + +//#include "EngineMinimal.h" +//#include "Core.h" +//#include "OnlineSessionInterface.h" +//#include "OnlineSessionSettings.h" +//#include "OnlineDelegateMacros.h" +//#include "OnlineSubsystem.h" +//#include "OnlineSubsystemImpl.h" +//#include "OnlineSubsystemUtils.h" +//#include "OnlineSubsystemUtilsModule.h" +//#include "ModuleManager.h" +//#include "OnlineSubsystemUtilsClasses.h" +//#include "BlueprintDataDefinitions.h" + + +/*#include "VoiceEngineImpl.h" +#include "VoiceInterfaceImpl.h" +#include "Voice.h"" +*/ + +// Found this in the steam controller, seems like a nice thought since steam is throwing errors +// Disable crazy warnings that claim that standard C library is "deprecated". +//#ifdef _MSC_VER +//#pragma warning(push) +//#pragma warning(disable:4996) +//#endif diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/SendFriendInviteCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/SendFriendInviteCallbackProxy.h new file mode 100644 index 0000000..ca5c352 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/SendFriendInviteCallbackProxy.h @@ -0,0 +1,49 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Engine/LocalPlayer.h" +#include "SendFriendInviteCallbackProxy.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(AdvancedSendFriendInviteLog, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBlueprintSendFriendInviteDelegate); + +UCLASS(MinimalAPI) +class USendFriendInviteCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the friends list successfully was retrieved + UPROPERTY(BlueprintAssignable) + FBlueprintSendFriendInviteDelegate OnSuccess; + + // Called when there was an error retrieving the friends list + UPROPERTY(BlueprintAssignable) + FBlueprintSendFriendInviteDelegate OnFailure; + + // Adds a friend who is using the defined UniqueNetId, some interfaces do now allow this function to be called (INCLUDING STEAM) + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedFriends") + static USendFriendInviteCallbackProxy* SendFriendInvite(UObject* WorldContextObject, APlayerController *PlayerController, const FBPUniqueNetId &UniqueNetIDInvited); + + virtual void Activate() override; + +private: + // Internal callback when the friends list is retrieved + void OnSendInviteComplete(int32 LocalPlayerNum, bool bWasSuccessful, const FUniqueNetId &InvitedPlayer, const FString &ListName, const FString &ErrorString); + + + // The player controller triggering things + TWeakObjectPtr PlayerControllerWeakPtr; + + // The UniqueNetID of the person to invite + FBPUniqueNetId cUniqueNetId; + + // The delegate to call on completion + FOnSendInviteComplete OnSendInviteCompleteDelegate; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/StartSessionCallbackProxyAdvanced.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/StartSessionCallbackProxyAdvanced.h new file mode 100644 index 0000000..246fc50 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/StartSessionCallbackProxyAdvanced.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "StartSessionCallbackProxyAdvanced.generated.h" + +UCLASS(MinimalAPI) +class UStartSessionCallbackProxyAdvanced : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + // Called when the session starts successfully + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there is an error starting the session + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + /** + * Starts a session with the default online subsystem. The session needs to be previously created by calling the "CreateAdvancedSession" node. + * @param WorldContextObject + */ + UFUNCTION( + BlueprintCallable + , meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject") + , Category = "Online|AdvancedSessions" + ) + static UStartSessionCallbackProxyAdvanced* StartAdvancedSession(const UObject* WorldContextObject); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when session start completes + void OnStartCompleted(FName SessionName, bool bWasSuccessful); + + // The delegate executed by the online subsystem + FOnStartSessionCompleteDelegate StartCompleteDelegate; + + // Handles to the registered delegates above + FDelegateHandle StartCompleteDelegateHandle; + + // The world context object in which this call is taking place + const UObject* WorldContextObject; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/UpdateSessionCallbackProxyAdvanced.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/UpdateSessionCallbackProxyAdvanced.h new file mode 100644 index 0000000..bd549a9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Classes/UpdateSessionCallbackProxyAdvanced.h @@ -0,0 +1,69 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "BlueprintDataDefinitions.h" +#include "UpdateSessionCallbackProxyAdvanced.generated.h" + +UCLASS(MinimalAPI) +class UUpdateSessionCallbackProxyAdvanced : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when the session was updated successfully + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnSuccess; + + // Called when there was an error updating the session + UPROPERTY(BlueprintAssignable) + FEmptyOnlineDelegate OnFailure; + + // Creates a session with the default online subsystem with advanced optional inputs, you MUST fill in all categories or it will pass in values that you didn't want as default values + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject",AutoCreateRefTerm="ExtraSettings"), Category = "Online|AdvancedSessions") + static UUpdateSessionCallbackProxyAdvanced* UpdateSession(UObject* WorldContextObject, const TArray &ExtraSettings, int32 PublicConnections = 100, int32 PrivateConnections = 0, bool bUseLAN = false, bool bAllowInvites = false, bool bAllowJoinInProgress = false, bool bRefreshOnlineData = true, bool bIsDedicatedServer = false, bool bShouldAdvertise = true); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + // Internal callback when session creation completes, calls StartSession + void OnUpdateCompleted(FName SessionName, bool bWasSuccessful); + + // The delegate executed by the online subsystem + FOnUpdateSessionCompleteDelegate OnUpdateSessionCompleteDelegate; + + // Handles to the registered delegates above + FDelegateHandle OnUpdateSessionCompleteDelegateHandle; + + // Number of public connections + int NumPublicConnections = 100; + + // Number of private connections + int NumPrivateConnections = 0; + + // Whether or not to search LAN + bool bUseLAN = false; + + // Whether or not to allow invites + bool bAllowInvites = true; + + // Store extra settings + TArray ExtraSettings; + + // Whether to update the online data + bool bRefreshOnlineData = true; + + // Allow joining in progress + bool bAllowJoinInProgress = true; + + // Update whether this is a dedicated server or not + bool bDedicatedServer = false; + + bool bShouldAdvertise = true; + + // The world context object in which this call is taking place + UObject* WorldContextObject; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedExternalUILibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedExternalUILibrary.cpp new file mode 100644 index 0000000..f4b1766 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedExternalUILibrary.cpp @@ -0,0 +1,160 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedExternalUILibrary.h" +#include "Engine/LocalPlayer.h" + + +//General Log +DEFINE_LOG_CATEGORY(AdvancedExternalUILog); + +void UAdvancedExternalUILibrary::ShowAccountUpgradeUI(const FBPUniqueNetId PlayerRequestingAccountUpgradeUI, EBlueprintResultSwitch &Result) +{ + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowAccountUpgradeUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ExternalUIInterface->ShowAccountUpgradeUI(*PlayerRequestingAccountUpgradeUI.GetUniqueNetId()); + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedExternalUILibrary::ShowProfileUI(const FBPUniqueNetId PlayerViewingProfile, const FBPUniqueNetId PlayerToViewProfileOf, EBlueprintResultSwitch &Result) +{ + + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowProfileUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ExternalUIInterface->ShowProfileUI(*PlayerViewingProfile.GetUniqueNetId(), *PlayerToViewProfileOf.GetUniqueNetId(), NULL); + Result = EBlueprintResultSwitch::OnSuccess; +} + + + +void UAdvancedExternalUILibrary::ShowWebURLUI(FString URLToShow, EBlueprintResultSwitch &Result, TArray& AllowedDomains, bool bEmbedded, bool bShowBackground, bool bShowCloseButton, int32 OffsetX, int32 OffsetY, int32 SizeX, int32 SizeY) +{ + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowWebURLUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + URLToShow = URLToShow.Replace(TEXT("http://"), TEXT("")); + URLToShow = URLToShow.Replace(TEXT("https://"), TEXT("")); + + FShowWebUrlParams Params; + Params.AllowedDomains = AllowedDomains; + Params.bEmbedded = bEmbedded; + Params.bShowBackground = bShowBackground; + Params.bShowCloseButton = bShowCloseButton; + Params.OffsetX = OffsetX; + Params.OffsetY = OffsetY; + Params.SizeX = SizeX; + Params.SizeY = SizeY; + + ExternalUIInterface->ShowWebURL(URLToShow, Params); + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedExternalUILibrary::CloseWebURLUI() +{ + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("CloseWebURLUI Failed to get External UI interface!")); + return; + } + + ExternalUIInterface->CloseWebURL(); +} + +void UAdvancedExternalUILibrary::ShowLeaderBoardUI(FString LeaderboardName, EBlueprintResultSwitch &Result) +{ + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowLeaderboardsUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ExternalUIInterface->ShowLeaderboardUI(LeaderboardName); + Result = EBlueprintResultSwitch::OnSuccess; + +} + + +void UAdvancedExternalUILibrary::ShowInviteUI(APlayerController *PlayerController, EBlueprintResultSwitch &Result) +{ + if (!PlayerController) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowInviteUI Had a bad Player Controller!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowInviteUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowInviteUI Failed to get ULocalPlayer for the given PlayerController!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ExternalUIInterface->ShowInviteUI(Player->GetControllerId(), NAME_GameSession); + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedExternalUILibrary::ShowFriendsUI(APlayerController *PlayerController, EBlueprintResultSwitch &Result) +{ + if (!PlayerController) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowFriendsUI Had a bad Player Controller!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineExternalUIPtr ExternalUIInterface = Online::GetExternalUIInterface(); + + if (!ExternalUIInterface.IsValid()) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowFriendsUI Failed to get External UI interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedExternalUILog, Warning, TEXT("ShowFriendsUI Failed to get ULocalPlayer for the given PlayerController!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ExternalUIInterface->ShowFriendsUI(Player->GetControllerId()); + Result = EBlueprintResultSwitch::OnSuccess; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsGameInstance.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsGameInstance.cpp new file mode 100644 index 0000000..47afb6a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsGameInstance.cpp @@ -0,0 +1,334 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedFriendsGameInstance.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerController.h" + +//General Log +DEFINE_LOG_CATEGORY(AdvancedFriendsInterfaceLog); + +UAdvancedFriendsGameInstance::UAdvancedFriendsGameInstance(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , bCallFriendInterfaceEventsOnPlayerControllers(true) + , bCallIdentityInterfaceEventsOnPlayerControllers(true) + , bCallVoiceInterfaceEventsOnPlayerControllers(true) + , bEnableTalkingStatusDelegate(true) + , SessionInviteReceivedDelegate(FOnSessionInviteReceivedDelegate::CreateUObject(this, &ThisClass::OnSessionInviteReceivedMaster)) + , SessionInviteAcceptedDelegate(FOnSessionUserInviteAcceptedDelegate::CreateUObject(this, &ThisClass::OnSessionInviteAcceptedMaster)) + , PlayerTalkingStateChangedDelegate(FOnPlayerTalkingStateChangedDelegate::CreateUObject(this, &ThisClass::OnPlayerTalkingStateChangedMaster)) + , PlayerLoginChangedDelegate(FOnLoginChangedDelegate::CreateUObject(this, &ThisClass::OnPlayerLoginChangedMaster)) + , PlayerLoginStatusChangedDelegate(FOnLoginStatusChangedDelegate::CreateUObject(this, &ThisClass::OnPlayerLoginStatusChangedMaster)) +{ +} + +void UAdvancedFriendsGameInstance::Shutdown() +{ + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(GetWorld()); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsGameInstance Failed to get session system!")); + //return; + } + else + { + // Clear all of the delegate handles here + SessionInterface->ClearOnSessionUserInviteAcceptedDelegate_Handle(SessionInviteAcceptedDelegateHandle); + SessionInterface->ClearOnSessionInviteReceivedDelegate_Handle(SessionInviteReceivedDelegateHandle); + } + + + if (bEnableTalkingStatusDelegate) + { + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(GetWorld()); + + if (VoiceInterface.IsValid()) + { + VoiceInterface->ClearOnPlayerTalkingStateChangedDelegate_Handle(PlayerTalkingStateChangedDelegateHandle); + } + else + { + + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get voice interface!")); + } + } + + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(GetWorld()); + + if (IdentityInterface.IsValid()) + { + IdentityInterface->ClearOnLoginChangedDelegate_Handle(PlayerLoginChangedDelegateHandle); + + + // I am just defaulting to player 1 + IdentityInterface->ClearOnLoginStatusChangedDelegate_Handle(0, PlayerLoginStatusChangedDelegateHandle); + } + + + Super::Shutdown(); +} + +void UAdvancedFriendsGameInstance::Init() +{ + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(GetWorld());//OnlineSub->GetSessionInterface(); + + if (SessionInterface.IsValid()) + { + // Currently doesn't store a handle or assign a delegate to any local player beyond the first.....should handle? + // Thought about directly handling it but friends for multiple players probably isn't required + // Iterating through the local player TArray only works if it has had players assigned to it, most of the online interfaces don't support + // Multiple logins either (IE: Steam) + SessionInviteAcceptedDelegateHandle = SessionInterface->AddOnSessionUserInviteAcceptedDelegate_Handle(SessionInviteAcceptedDelegate); + + SessionInviteReceivedDelegateHandle = SessionInterface->AddOnSessionInviteReceivedDelegate_Handle(SessionInviteReceivedDelegate); + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get session interface!")); + //return; + } + + // Beginning work on the voice interface + if (bEnableTalkingStatusDelegate) + { + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(GetWorld()); + + if (VoiceInterface.IsValid()) + { + PlayerTalkingStateChangedDelegateHandle = VoiceInterface->AddOnPlayerTalkingStateChangedDelegate_Handle(PlayerTalkingStateChangedDelegate); + } + else + { + + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get voice interface!")); + } + } + + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(GetWorld()); + + if (IdentityInterface.IsValid()) + { + PlayerLoginChangedDelegateHandle = IdentityInterface->AddOnLoginChangedDelegate_Handle(PlayerLoginChangedDelegate); + + // Just defaulting to player 1 + PlayerLoginStatusChangedDelegateHandle = IdentityInterface->AddOnLoginStatusChangedDelegate_Handle(0, PlayerLoginStatusChangedDelegate); + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get identity interface!")); + } + + + Super::Init(); +} + +/*void UAdvancedFriendsGameInstance::PostLoad() +{ + Super::PostLoad(); +}*/ + + +// Removed because it never gets called by the online subsystems +/*void UAdvancedFriendsGameInstance::OnSessionInviteReceivedMaster(const FUniqueNetId &InvitedPlayer, const FUniqueNetId &FriendInviting, const FOnlineSessionSearchResult& Session) +{ + // Just call the blueprint event to let the user handle this + + FBPUniqueNetId IP, FI; + + IP.SetUniqueNetId(&InvitedPlayer); + + FI.SetUniqueNetId(&FriendInviting); + + FBlueprintSessionResult BPS; + BPS.OnlineResult = Session; + OnSessionInviteReceived(IP,FI,BPS); + + TArray& PlayerArray = GetWorld()->GetGameState()->PlayerArray; + const TArray&ControllerArray = this->GetLocalPlayers(); + + for (int i = 0; i < ControllerArray.Num(); i++) + { + if (*PlayerArray[ControllerArray[i]->PlayerController->NetPlayerIndex]->UniqueId.GetUniqueNetId().Get() == InvitedPlayer) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (ControllerArray[i]->PlayerController->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnSessionInviteReceived(ControllerArray[i]->PlayerController, FI, BPS); + } + break; + } + } +}*/ + +void UAdvancedFriendsGameInstance::OnPlayerLoginStatusChangedMaster(int32 PlayerNum, ELoginStatus::Type PreviousStatus, ELoginStatus::Type NewStatus, const FUniqueNetId & NewPlayerUniqueNetID) +{ + EBPLoginStatus OrigStatus = (EBPLoginStatus)PreviousStatus; + EBPLoginStatus CurrentStatus = (EBPLoginStatus)NewStatus; + FBPUniqueNetId PlayerID; + PlayerID.SetUniqueNetId(&NewPlayerUniqueNetID); + + OnPlayerLoginStatusChanged(PlayerNum, OrigStatus,CurrentStatus,PlayerID); + + + if (bCallIdentityInterfaceEventsOnPlayerControllers) + { + APlayerController* Player = UGameplayStatics::GetPlayerController(GetWorld(), PlayerNum); + + if (Player != NULL) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (Player->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnPlayerLoginStatusChanged(Player, OrigStatus, CurrentStatus, PlayerID); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get a controller with the specified index in OnPlayerLoginStatusChangedMaster!")); + } + } +} + +void UAdvancedFriendsGameInstance::OnPlayerLoginChangedMaster(int32 PlayerNum) +{ + OnPlayerLoginChanged(PlayerNum); + + if (bCallIdentityInterfaceEventsOnPlayerControllers) + { + APlayerController* Player = UGameplayStatics::GetPlayerController(GetWorld(), PlayerNum); + + if (Player != NULL) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (Player->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnPlayerLoginChanged(Player, PlayerNum); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get a controller with the specified index in OnPlayerLoginChanged!")); + } + } +} + +void UAdvancedFriendsGameInstance::OnPlayerTalkingStateChangedMaster(TSharedRef PlayerId, bool bIsTalking) +{ + FBPUniqueNetId PlayerTalking; + PlayerTalking.SetUniqueNetId(PlayerId); + OnPlayerTalkingStateChanged(PlayerTalking, bIsTalking); + + if (bCallVoiceInterfaceEventsOnPlayerControllers) + { + APlayerController* Player = NULL; + + for (const ULocalPlayer* LPlayer : LocalPlayers) + { + Player = UGameplayStatics::GetPlayerController(GetWorld(), LPlayer->GetControllerId()); + + if (Player != NULL) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (Player->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnPlayerVoiceStateChanged(Player, PlayerTalking, bIsTalking); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get a controller with the specified index in OnVoiceStateChanged!")); + } + } + } +} + +void UAdvancedFriendsGameInstance::OnSessionInviteReceivedMaster(const FUniqueNetId & PersonInvited, const FUniqueNetId & PersonInviting, const FString& AppId, const FOnlineSessionSearchResult& SessionToJoin) +{ + if (SessionToJoin.IsValid()) + { + FBlueprintSessionResult BluePrintResult; + BluePrintResult.OnlineResult = SessionToJoin; + + FBPUniqueNetId PInvited; + PInvited.SetUniqueNetId(&PersonInvited); + + FBPUniqueNetId PInviting; + PInviting.SetUniqueNetId(&PersonInviting); + + + TArray PlayerList; + GEngine->GetAllLocalPlayerControllers(PlayerList); + + APlayerController* Player = NULL; + + int32 LocalPlayer = 0; + for (int i = 0; i < PlayerList.Num(); i++) + { + if (*PlayerList[i]->PlayerState->GetUniqueId().GetUniqueNetId() == PersonInvited) + { + LocalPlayer = i; + Player = PlayerList[i]; + break; + } + } + + OnSessionInviteReceived(LocalPlayer, PInviting, AppId, BluePrintResult); + + //IAdvancedFriendsInterface* TheInterface = NULL; + + if (Player != NULL) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (Player->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnSessionInviteReceived(Player, PInviting, BluePrintResult); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get a controller with the specified index in OnSessionInviteReceived!")); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Return a bad search result in OnSessionInviteReceived!")); + } +} + +void UAdvancedFriendsGameInstance::OnSessionInviteAcceptedMaster(const bool bWasSuccessful, int32 LocalPlayer, TSharedPtr PersonInvited, const FOnlineSessionSearchResult& SessionToJoin) +{ + if (bWasSuccessful) + { + if (SessionToJoin.IsValid()) + { + + FBlueprintSessionResult BluePrintResult; + BluePrintResult.OnlineResult = SessionToJoin; + + FBPUniqueNetId PInvited; + PInvited.SetUniqueNetId(PersonInvited); + + OnSessionInviteAccepted(LocalPlayer,PInvited, BluePrintResult); + + APlayerController* Player = UGameplayStatics::GetPlayerController(GetWorld(), LocalPlayer); + + //IAdvancedFriendsInterface* TheInterface = NULL; + + if (Player != NULL) + { + //Run the Event specific to the actor, if the actor has the interface, otherwise ignore + if (Player->GetClass()->ImplementsInterface(UAdvancedFriendsInterface::StaticClass())) + { + IAdvancedFriendsInterface::Execute_OnSessionInviteAccepted(Player,PInvited, BluePrintResult); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Failed to get a controller with the specified index in OnSessionInviteAccepted!")); + } + } + else + { + UE_LOG(AdvancedFriendsInterfaceLog, Warning, TEXT("UAdvancedFriendsInstance Return a bad search result in OnSessionInviteAccepted!")); + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsInterface.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsInterface.cpp new file mode 100644 index 0000000..92e5138 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsInterface.cpp @@ -0,0 +1,9 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedFriendsInterface.h" + + + +UAdvancedFriendsInterface::UAdvancedFriendsInterface(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsLibrary.cpp new file mode 100644 index 0000000..1af7a5b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedFriendsLibrary.cpp @@ -0,0 +1,274 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedFriendsLibrary.h" + + + +// This is taken directly from UE4 - OnlineSubsystemSteamPrivatePCH.h as a fix for the array_count macro + +//General Log +DEFINE_LOG_CATEGORY(AdvancedFriendsLog); + +void UAdvancedFriendsLibrary::SendSessionInviteToFriends(APlayerController *PlayerController, const TArray &Friends, EBlueprintResultSwitch &Result) +{ + if (!PlayerController) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Had a bad Player Controller!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (Friends.Num() < 1) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Had no friends in invitation array!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Failed to get session interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend failed to get LocalPlayer!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + TArray> List; + for (int i = 0; i < Friends.Num(); i++) + { + TSharedRef val(Friends[i].UniqueNetId.ToSharedRef()); + //TSharedRef val(Friends[i].GetUniqueNetId()); + List.Add(val); + } + + if (SessionInterface->SendSessionInviteToFriends(Player->GetControllerId(), NAME_GameSession, List)) + { + Result = EBlueprintResultSwitch::OnSuccess; + return; + } + + Result = EBlueprintResultSwitch::OnFailure; + return; +} + +void UAdvancedFriendsLibrary::SendSessionInviteToFriend(APlayerController *PlayerController, const FBPUniqueNetId &FriendUniqueNetId, EBlueprintResultSwitch &Result) +{ + if (!PlayerController) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Had a bad Player Controller!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (!FriendUniqueNetId.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Had a bad UniqueNetId!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend Failed to get session interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("SendSessionInviteToFriend failed to get LocalPlayer!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (SessionInterface->SendSessionInviteToFriend(Player->GetControllerId(), NAME_GameSession, *FriendUniqueNetId.GetUniqueNetId())) + { + Result = EBlueprintResultSwitch::OnSuccess; + return; + } + + Result = EBlueprintResultSwitch::OnFailure; + return; +} + +void UAdvancedFriendsLibrary::GetFriend(APlayerController *PlayerController, const FBPUniqueNetId FriendUniqueNetId, FBPFriendInfo &Friend) +{ + + if (!PlayerController) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriend Had a bad Player Controller!")); + return; + } + + if (!FriendUniqueNetId.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriend Had a bad UniqueNetId!")); + return; + } + + IOnlineFriendsPtr FriendsInterface = Online::GetFriendsInterface(); + + if (!FriendsInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriend Failed to get friends interface!")); + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriend failed to get LocalPlayer!")); + return; + } + + TSharedPtr fr = FriendsInterface->GetFriend(Player->GetControllerId(), *FriendUniqueNetId.GetUniqueNetId(), EFriendsLists::ToString(EFriendsLists::Default)); + if (fr.IsValid()) + { + FOnlineUserPresence pres = fr->GetPresence(); + Friend.DisplayName = fr->GetDisplayName(); + Friend.OnlineState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + Friend.RealName = fr->GetRealName(); + Friend.UniqueNetId.SetUniqueNetId(fr->GetUserId()); + Friend.bIsPlayingSameGame = pres.bIsPlayingThisGame; + + Friend.PresenceInfo.bHasVoiceSupport = pres.bHasVoiceSupport; + Friend.PresenceInfo.bIsJoinable = pres.bIsJoinable; + Friend.PresenceInfo.bIsOnline = pres.bIsOnline; + Friend.PresenceInfo.bIsPlaying = pres.bIsPlaying; + Friend.PresenceInfo.bIsPlayingThisGame = pres.bIsPlayingThisGame; + Friend.PresenceInfo.PresenceState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + Friend.PresenceInfo.StatusString = pres.Status.StatusStr; + } +} + +void UAdvancedFriendsLibrary::IsAFriend(APlayerController *PlayerController, const FBPUniqueNetId UniqueNetId, bool &IsFriend) +{ + if (!PlayerController) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("IsAFriend Had a bad Player Controller!")); + return; + } + + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("IsAFriend Had a bad UniqueNetId!")); + return; + } + + IOnlineFriendsPtr FriendsInterface = Online::GetFriendsInterface(); + + if (!FriendsInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("IsAFriend Failed to get friends interface!")); + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("IsAFriend Failed to get LocalPlayer!")); + return; + } + + IsFriend = FriendsInterface->IsFriend(Player->GetControllerId(), *UniqueNetId.GetUniqueNetId(), EFriendsLists::ToString(EFriendsLists::Default)); +} + +void UAdvancedFriendsLibrary::GetStoredRecentPlayersList(FBPUniqueNetId UniqueNetId, TArray &PlayersList) +{ + IOnlineFriendsPtr FriendsInterface = Online::GetFriendsInterface(); + + if (!FriendsInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetRecentPlayersList Failed to get friends interface!")); + return; + } + + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetRecentPlayersList Failed was given an invalid UniqueNetId!")); + return; + } + + TArray< TSharedRef > PlayerList; + + // For now getting all namespaces + FriendsInterface->GetRecentPlayers(*(UniqueNetId.GetUniqueNetId()),"", PlayerList); + + for (int32 i = 0; i < PlayerList.Num(); i++) + { + TSharedRef Player = PlayerList[i]; + FBPOnlineRecentPlayer BPF; + BPF.DisplayName = Player->GetDisplayName(); + BPF.RealName = Player->GetRealName(); + BPF.UniqueNetId.SetUniqueNetId(Player->GetUserId()); + PlayersList.Add(BPF); + } +} + +void UAdvancedFriendsLibrary::GetStoredFriendsList(APlayerController *PlayerController, TArray &FriendsList) +{ + + if (!PlayerController) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriendsList Had a bad Player Controller!")); + return; + } + + IOnlineFriendsPtr FriendsInterface = Online::GetFriendsInterface(); + + if (!FriendsInterface.IsValid()) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriendsList Failed to get friends interface!")); + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedFriendsLog, Warning, TEXT("GetFriendsList Failed to get LocalPlayer!")); + return; + } + + + TArray< TSharedRef > FriendList; + FriendsInterface->GetFriendsList(Player->GetControllerId(), EFriendsLists::ToString((EFriendsLists::Default)), FriendList); + + for (int32 i = 0; i < FriendList.Num(); i++) + { + TSharedRef Friend = FriendList[i]; + FBPFriendInfo BPF; + FOnlineUserPresence pres = Friend->GetPresence(); + + BPF.OnlineState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + BPF.DisplayName = Friend->GetDisplayName(); + BPF.RealName = Friend->GetRealName(); + BPF.UniqueNetId.SetUniqueNetId(Friend->GetUserId()); + BPF.bIsPlayingSameGame = pres.bIsPlayingThisGame; + + BPF.PresenceInfo.bIsOnline = pres.bIsOnline; + BPF.PresenceInfo.bHasVoiceSupport = pres.bHasVoiceSupport; + BPF.PresenceInfo.bIsPlaying = pres.bIsPlaying; + BPF.PresenceInfo.PresenceState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + BPF.PresenceInfo.StatusString = pres.Status.StatusStr; + BPF.PresenceInfo.bIsJoinable = pres.bIsJoinable; + BPF.PresenceInfo.bIsPlayingThisGame = pres.bIsPlayingThisGame; + + FriendsList.Add(BPF); + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedIdentityLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedIdentityLibrary.cpp new file mode 100644 index 0000000..a44d5ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedIdentityLibrary.cpp @@ -0,0 +1,235 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedIdentityLibrary.h" + +//General Log +DEFINE_LOG_CATEGORY(AdvancedIdentityLog); + + +void UAdvancedIdentityLibrary::GetPlayerAuthToken(APlayerController * PlayerController, FString & AuthToken, EBlueprintResultSwitch &Result) +{ + if (!PlayerController) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetPlayerAuthToken was passed a bad player controller!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + ULocalPlayer* Player = Cast(PlayerController->Player); + + if (!Player) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetPlayerAuthToken failed to get LocalPlayer!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(); + + if (!IdentityInterface.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetPlayerAuthToken Failed to get identity interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + AuthToken = IdentityInterface->GetAuthToken(Player->GetControllerId()); + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedIdentityLibrary::GetPlayerNickname(const FBPUniqueNetId & UniqueNetID, FString & PlayerNickname) +{ + if (!UniqueNetID.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetPlayerNickname was passed a bad player uniquenetid!")); + return; + } + + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(); + + if (!IdentityInterface.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetPlayerNickname Failed to get identity interface!")); + return; + } + PlayerNickname = IdentityInterface->GetPlayerNickname(*UniqueNetID.GetUniqueNetId()); +} + + +void UAdvancedIdentityLibrary::GetLoginStatus(const FBPUniqueNetId & UniqueNetID, EBPLoginStatus & LoginStatus, EBlueprintResultSwitch &Result) +{ + if (!UniqueNetID.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetLoginStatus was passed a bad player uniquenetid!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(); + + if (!IdentityInterface.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetLoginStatus Failed to get identity interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + LoginStatus = (EBPLoginStatus)IdentityInterface->GetLoginStatus(*UniqueNetID.GetUniqueNetId()); + Result = EBlueprintResultSwitch::OnSuccess; +} + + +void UAdvancedIdentityLibrary::GetAllUserAccounts(TArray & AccountInfos, EBlueprintResultSwitch &Result) +{ + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(); + + if (!IdentityInterface.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetAllUserAccounts Failed to get identity interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + TArray> accountInfos = IdentityInterface->GetAllUserAccounts(); + + for (int i = 0; i < accountInfos.Num(); ++i) + { + AccountInfos.Add(FBPUserOnlineAccount(accountInfos[i])); + } + + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedIdentityLibrary::GetUserAccount(const FBPUniqueNetId & UniqueNetId, FBPUserOnlineAccount & AccountInfo, EBlueprintResultSwitch &Result) +{ + IOnlineIdentityPtr IdentityInterface = Online::GetIdentityInterface(); + + if(!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccount was passed a bad unique net id!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (!IdentityInterface.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccount Failed to get identity interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + TSharedPtr accountInfo = IdentityInterface->GetUserAccount(*UniqueNetId.GetUniqueNetId()); + + if (!accountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccount Failed to get the account!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + AccountInfo.UserAccountInfo = accountInfo; + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedIdentityLibrary::GetUserAccountAccessToken(const FBPUserOnlineAccount & AccountInfo, FString & AccessToken) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountAccessToken was passed an invalid account!")); + return; + } + + AccessToken = AccountInfo.UserAccountInfo->GetAccessToken(); +} + +void UAdvancedIdentityLibrary::GetUserAccountAuthAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, FString & AuthAttribute, EBlueprintResultSwitch &Result) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountAuthAttribute was passed an invalid account!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (!AccountInfo.UserAccountInfo->GetAuthAttribute(AttributeName, AuthAttribute)) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountAuthAttribute couldn't find the attribute!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedIdentityLibrary::SetUserAccountAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, const FString & NewAttributeValue, EBlueprintResultSwitch &Result) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("SetUserAccountAuthAttribute was passed an invalid account!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (!AccountInfo.UserAccountInfo->SetUserAttribute(AttributeName, NewAttributeValue)) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("SetUserAccountAuthAttribute was unable to set the attribute!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedIdentityLibrary::GetUserID(const FBPUserOnlineAccount & AccountInfo, FBPUniqueNetId & UniqueNetID) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserID was passed an invalid account!")); + return; + } + + + UniqueNetID.SetUniqueNetId(AccountInfo.UserAccountInfo->GetUserId()); +} + +void UAdvancedIdentityLibrary::GetUserAccountRealName(const FBPUserOnlineAccount & AccountInfo, FString & UserName) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountRealName was passed an invalid account!")); + return; + } + + + UserName = AccountInfo.UserAccountInfo->GetRealName(); +} + +void UAdvancedIdentityLibrary::GetUserAccountDisplayName(const FBPUserOnlineAccount & AccountInfo, FString & DisplayName) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountDisplayName was passed an invalid account!")); + return; + } + + + DisplayName = AccountInfo.UserAccountInfo->GetDisplayName(); +} + +void UAdvancedIdentityLibrary::GetUserAccountAttribute(const FBPUserOnlineAccount & AccountInfo, const FString & AttributeName, FString & AttributeValue, EBlueprintResultSwitch &Result) +{ + if (!AccountInfo.UserAccountInfo.IsValid()) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountAttribute was passed an invalid account!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (!AccountInfo.UserAccountInfo->GetUserAttribute(AttributeName, AttributeValue)) + { + UE_LOG(AdvancedIdentityLog, Warning, TEXT("GetUserAccountAttribute failed to get user attribute!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + Result = EBlueprintResultSwitch::OnSuccess; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessions.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessions.cpp new file mode 100644 index 0000000..8a12271 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessions.cpp @@ -0,0 +1,12 @@ +//#include "StandAlonePrivatePCH.h" +#include "AdvancedSessions.h" + +void AdvancedSessions::StartupModule() +{ +} + +void AdvancedSessions::ShutdownModule() +{ +} + +IMPLEMENT_MODULE(AdvancedSessions, AdvancedSessions) \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessionsLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessionsLibrary.cpp new file mode 100644 index 0000000..9b2b6bf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedSessionsLibrary.cpp @@ -0,0 +1,547 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedSessionsLibrary.h" +#include "GameFramework/PlayerState.h" +#include "GameFramework/GameStateBase.h" + +//General Log +DEFINE_LOG_CATEGORY(AdvancedSessionsLog); + + +bool UAdvancedSessionsLibrary::KickPlayer(UObject* WorldContextObject, APlayerController* PlayerToKick, FText KickReason) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + + if (World) + { + if (AGameModeBase* GameMode = World->GetAuthGameMode()) + { + if (GameMode->GameSession) + { + return GameMode->GameSession->KickPlayer(PlayerToKick, KickReason); + } + } + } + + return false; +} + +bool UAdvancedSessionsLibrary::BanPlayer(UObject* WorldContextObject, APlayerController* PlayerToBan, FText BanReason) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + + if (World) + { + if (AGameModeBase* GameMode = World->GetAuthGameMode()) + { + if (GameMode->GameSession) + { + return GameMode->GameSession->BanPlayer(PlayerToBan, BanReason); + } + } + } + + return false; +} + +bool UAdvancedSessionsLibrary::IsValidSession(const FBlueprintSessionResult & SessionResult) +{ + return SessionResult.OnlineResult.IsValid(); +} + +void UAdvancedSessionsLibrary::GetSessionID_AsString(const FBlueprintSessionResult & SessionResult, FString& SessionID) +{ + const TSharedPtr SessionInfo = SessionResult.OnlineResult.Session.SessionInfo; + if (SessionInfo.IsValid() && SessionInfo->IsValid() && SessionInfo->GetSessionId().IsValid()) + { + SessionID = SessionInfo->GetSessionId().ToString(); + return; + } + + // Zero the string out if we didn't have a valid one, in case this is called in c++ + SessionID.Empty(); +} + +void UAdvancedSessionsLibrary::GetCurrentSessionID_AsString(UObject* WorldContextObject, FString& SessionID) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(World); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetCurrentSessionID_AsString couldn't get the session interface!")); + SessionID.Empty(); + return; + } + + const FNamedOnlineSession* Session = SessionInterface->GetNamedSession(NAME_GameSession); + if (Session != nullptr) + { + const TSharedPtr SessionInfo = Session->SessionInfo; + if (SessionInfo.IsValid() && SessionInfo->IsValid() && SessionInfo->GetSessionId().IsValid()) + { + SessionID = SessionInfo->GetSessionId().ToString(); + return; + } + } + + // Zero the string out if we didn't have a valid one, in case this is called in c++ + SessionID.Empty(); +} + +void UAdvancedSessionsLibrary::GetCurrentUniqueBuildID(int32 &UniqueBuildId) +{ + UniqueBuildId = GetBuildUniqueId(); +} + +void UAdvancedSessionsLibrary::GetUniqueBuildID(FBlueprintSessionResult SessionResult, int32 &UniqueBuildId) +{ + UniqueBuildId = SessionResult.OnlineResult.Session.SessionSettings.BuildUniqueId; +} + +FName UAdvancedSessionsLibrary::GetSessionPropertyKey(const FSessionPropertyKeyPair& SessionProperty) +{ + return SessionProperty.Key; +} + +void UAdvancedSessionsLibrary::FindSessionPropertyByName(const TArray& ExtraSettings, FName SettingName, EBlueprintResultSwitch &Result, FSessionPropertyKeyPair& OutProperty) +{ + const FSessionPropertyKeyPair* prop = ExtraSettings.FindByPredicate([&](const FSessionPropertyKeyPair& it) {return it.Key == SettingName; }); + if (prop) + { + Result = EBlueprintResultSwitch::OnSuccess; + OutProperty = *prop; + return; + } + + Result = EBlueprintResultSwitch::OnFailure; +} + +void UAdvancedSessionsLibrary::FindSessionPropertyIndexByName(const TArray& ExtraSettings, FName SettingName, EBlueprintResultSwitch &Result, int32& OutIndex) +{ + OutIndex = ExtraSettings.IndexOfByPredicate([&](const FSessionPropertyKeyPair& it) {return it.Key == SettingName; }); + + Result = OutIndex != INDEX_NONE ? EBlueprintResultSwitch::OnSuccess : EBlueprintResultSwitch::OnFailure; +} + +void UAdvancedSessionsLibrary::AddOrModifyExtraSettings(UPARAM(ref) TArray & SettingsArray, UPARAM(ref) TArray & NewOrChangedSettings, TArray & ModifiedSettingsArray) +{ + ModifiedSettingsArray = SettingsArray; + + bool bFoundSetting = false; + // For each new setting + for (const FSessionPropertyKeyPair& Setting : NewOrChangedSettings) + { + bFoundSetting = false; + + for (FSessionPropertyKeyPair & itr : ModifiedSettingsArray) + { + // Manually comparing the keys + if (itr.Key == Setting.Key) + { + bFoundSetting = true; + itr.Data = Setting.Data; + } + } + + // If it was not found, add to the array instead + if (!bFoundSetting) + { + ModifiedSettingsArray.Add(Setting); + } + } + +} + +void UAdvancedSessionsLibrary::GetExtraSettings(FBlueprintSessionResult SessionResult, TArray & ExtraSettings) +{ + FSessionPropertyKeyPair NewSetting; + for (auto& Elem : SessionResult.OnlineResult.Session.SessionSettings.Settings) + { + NewSetting.Key = Elem.Key; + NewSetting.Data = Elem.Value.Data; + ExtraSettings.Add(NewSetting); + } +} + +void UAdvancedSessionsLibrary::GetSessionState(UObject* WorldContextObject, EBPOnlineSessionState &SessionState) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(World); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetSessionState couldn't get the session interface!")); + return; + } + + SessionState = ((EBPOnlineSessionState)SessionInterface->GetSessionState(NAME_GameSession)); +} + +void UAdvancedSessionsLibrary::GetSessionSettings(UObject* WorldContextObject, int32 &NumConnections, int32 &NumPrivateConnections, bool &bIsLAN, bool &bIsDedicated, bool &bAllowInvites, bool &bAllowJoinInProgress, bool &bIsAnticheatEnabled, int32 &BuildUniqueID, TArray &ExtraSettings, EBlueprintResultSwitch &Result) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(World); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetSessionSettings couldn't get the session interface!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + FOnlineSessionSettings* settings = SessionInterface->GetSessionSettings(NAME_GameSession); + if (!settings) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetSessionSettings couldn't get the session settings!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + BuildUniqueID = settings->BuildUniqueId; + NumConnections = settings->NumPublicConnections; + NumPrivateConnections = settings->NumPrivateConnections; + bIsLAN = settings->bIsLANMatch; + bIsDedicated = settings->bIsDedicated; + bIsAnticheatEnabled = settings->bAntiCheatProtected; + bAllowInvites = settings->bAllowInvites; + bAllowJoinInProgress = settings->bAllowJoinInProgress; + + FSessionPropertyKeyPair NewSetting; + + for (auto& Elem : settings->Settings) + { + NewSetting.Key = Elem.Key; + NewSetting.Data = Elem.Value.Data; + ExtraSettings.Add(NewSetting); + } + + Result = EBlueprintResultSwitch::OnSuccess; +} + +void UAdvancedSessionsLibrary::IsPlayerInSession(UObject* WorldContextObject, const FBPUniqueNetId &PlayerToCheck, bool &bIsInSession) +{ + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + IOnlineSessionPtr SessionInterface = Online::GetSessionInterface(World); + + if (!SessionInterface.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("IsPlayerInSession couldn't get the session interface!")); + bIsInSession = false; + return; + } + + bIsInSession = SessionInterface->IsPlayerInSession(NAME_GameSession, *PlayerToCheck.GetUniqueNetId()); +} + +FSessionsSearchSetting UAdvancedSessionsLibrary::MakeLiteralSessionSearchProperty(FSessionPropertyKeyPair SessionSearchProperty, EOnlineComparisonOpRedux ComparisonOp) +{ + FSessionsSearchSetting setting; + setting.PropertyKeyPair = SessionSearchProperty; + setting.ComparisonOp = ComparisonOp; + + return setting; +} + +FSessionPropertyKeyPair UAdvancedSessionsLibrary::MakeLiteralSessionPropertyByte(FName Key, uint8 Value) +{ + FSessionPropertyKeyPair Prop; + Prop.Key = Key; + Prop.Data.SetValue((int32)Value); + return Prop; +} + +FSessionPropertyKeyPair UAdvancedSessionsLibrary::MakeLiteralSessionPropertyBool(FName Key, bool Value) +{ + FSessionPropertyKeyPair Prop; + Prop.Key = Key; + Prop.Data.SetValue(Value); + return Prop; +} + +FSessionPropertyKeyPair UAdvancedSessionsLibrary::MakeLiteralSessionPropertyString(FName Key, FString Value) +{ + FSessionPropertyKeyPair Prop; + Prop.Key = Key; + Prop.Data.SetValue(Value); + return Prop; +} + +FSessionPropertyKeyPair UAdvancedSessionsLibrary::MakeLiteralSessionPropertyInt(FName Key, int32 Value) +{ + FSessionPropertyKeyPair Prop; + Prop.Key = Key; + Prop.Data.SetValue(Value); + return Prop; +} + +FSessionPropertyKeyPair UAdvancedSessionsLibrary::MakeLiteralSessionPropertyFloat(FName Key, float Value) +{ + FSessionPropertyKeyPair Prop; + Prop.Key = Key; + Prop.Data.SetValue(Value); + return Prop; +} + +void UAdvancedSessionsLibrary::GetSessionPropertyByte(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, uint8 &SettingValue) +{ + for (FSessionPropertyKeyPair itr : ExtraSettings) + { + if (itr.Key == SettingName) + { + if (itr.Data.GetType() == EOnlineKeyValuePairDataType::Int32) + { + int32 Val; + itr.Data.GetValue(Val); + SettingValue = (uint8)(Val); + SearchResult = ESessionSettingSearchResult::Found; + } + else + { + SearchResult = ESessionSettingSearchResult::WrongType; + } + return; + } + } + + SearchResult = ESessionSettingSearchResult::NotFound; + return; +} + +void UAdvancedSessionsLibrary::GetSessionPropertyBool(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, bool &SettingValue) +{ + for (FSessionPropertyKeyPair itr : ExtraSettings) + { + if (itr.Key == SettingName) + { + if (itr.Data.GetType() == EOnlineKeyValuePairDataType::Bool) + { + itr.Data.GetValue(SettingValue); + SearchResult = ESessionSettingSearchResult::Found; + } + else + { + SearchResult = ESessionSettingSearchResult::WrongType; + } + return; + } + } + + SearchResult = ESessionSettingSearchResult::NotFound; + return; +} + +void UAdvancedSessionsLibrary::GetSessionPropertyString(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, FString &SettingValue) +{ + for (FSessionPropertyKeyPair itr : ExtraSettings) + { + if (itr.Key == SettingName) + { + if (itr.Data.GetType() == EOnlineKeyValuePairDataType::String) + { + itr.Data.GetValue(SettingValue); + SearchResult = ESessionSettingSearchResult::Found; + } + else + { + SearchResult = ESessionSettingSearchResult::WrongType; + } + return; + } + } + + SearchResult = ESessionSettingSearchResult::NotFound; + return; +} + +void UAdvancedSessionsLibrary::GetSessionPropertyInt(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, int32 &SettingValue) +{ + for (FSessionPropertyKeyPair itr : ExtraSettings) + { + if (itr.Key == SettingName) + { + if (itr.Data.GetType() == EOnlineKeyValuePairDataType::Int32) + { + itr.Data.GetValue(SettingValue); + SearchResult = ESessionSettingSearchResult::Found; + } + else + { + SearchResult = ESessionSettingSearchResult::WrongType; + } + return; + } + } + + SearchResult = ESessionSettingSearchResult::NotFound; + return; +} + +void UAdvancedSessionsLibrary::GetSessionPropertyFloat(const TArray & ExtraSettings, FName SettingName, ESessionSettingSearchResult &SearchResult, float &SettingValue) +{ + for (FSessionPropertyKeyPair itr : ExtraSettings) + { + if (itr.Key == SettingName) + { + if (itr.Data.GetType() == EOnlineKeyValuePairDataType::Float) + { + itr.Data.GetValue(SettingValue); + SearchResult = ESessionSettingSearchResult::Found; + } + else + { + SearchResult = ESessionSettingSearchResult::WrongType; + } + return; + } + } + + SearchResult = ESessionSettingSearchResult::NotFound; + return; +} + + +bool UAdvancedSessionsLibrary::HasOnlineSubsystem(FName SubSystemName) +{ + return IOnlineSubsystem::DoesInstanceExist(SubSystemName); +} + +void UAdvancedSessionsLibrary::GetNetPlayerIndex(APlayerController *PlayerController, int32 &NetPlayerIndex) +{ + if (!PlayerController) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetNetPlayerIndex received a bad PlayerController!")); + NetPlayerIndex = 0; + return; + } + + NetPlayerIndex = PlayerController->NetPlayerIndex; + return; +} + +void UAdvancedSessionsLibrary::UniqueNetIdToString(const FBPUniqueNetId& UniqueNetId, FString &String) +{ + const FUniqueNetId * ID = UniqueNetId.GetUniqueNetId(); + + if ( !ID ) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("UniqueNetIdToString received a bad UniqueNetId!")); + String = "ERROR, BAD UNIQUE NET ID"; + } + else + String = ID->ToString(); +} + + +void UAdvancedSessionsLibrary::GetUniqueNetID(APlayerController *PlayerController, FBPUniqueNetId &UniqueNetId) +{ + if (!PlayerController) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetUniqueNetIdFromController received a bad PlayerController!")); + return; + } + + if (APlayerState* PlayerState = (PlayerController != NULL) ? PlayerController->PlayerState : NULL) + { + UniqueNetId.SetUniqueNetId(PlayerState->GetUniqueId().GetUniqueNetId()); + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetUniqueNetIdFromController couldn't get the player uniquenetid!")); + } + return; + } +} + +void UAdvancedSessionsLibrary::GetUniqueNetIDFromPlayerState(APlayerState *PlayerState, FBPUniqueNetId &UniqueNetId) +{ + if (!PlayerState) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetUniqueNetIdFromPlayerState received a bad PlayerState!")); + return; + } + + UniqueNetId.SetUniqueNetId(PlayerState->GetUniqueId().GetUniqueNetId()); + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetUniqueNetIdFromPlayerState couldn't get the player uniquenetid!")); + } + return; +} + +bool UAdvancedSessionsLibrary::IsValidUniqueNetID(const FBPUniqueNetId &UniqueNetId) +{ + return UniqueNetId.IsValid(); +} + +bool UAdvancedSessionsLibrary::EqualEqual_UNetIDUnetID(const FBPUniqueNetId &A, const FBPUniqueNetId &B) +{ + return ((A.IsValid() && B.IsValid()) && (*A.GetUniqueNetId() == *B.GetUniqueNetId())); +} + +void UAdvancedSessionsLibrary::SetPlayerName(APlayerController *PlayerController, FString PlayerName) +{ + if (!PlayerController) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("SetLocalPlayerNameFromController Bad Player Controller!")); + return; + } + + if (APlayerState* PlayerState = (PlayerController != NULL) ? PlayerController->PlayerState : NULL) + { + PlayerState->SetPlayerName(PlayerName); + return; + } + else + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("SetLocalPlayerNameFromController had a bad player state!")); + } +} + +void UAdvancedSessionsLibrary::GetPlayerName(APlayerController *PlayerController, FString &PlayerName) +{ + if (!PlayerController) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetLocalPlayerNameFromController Bad Player Controller!")); + return; + } + + if (APlayerState* PlayerState = (PlayerController != NULL) ? PlayerController->PlayerState : NULL) + { + PlayerName = PlayerState->GetPlayerName(); + return; + } + else + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetLocalPlayerNameFromController had a bad player state!")); + } +} + +void UAdvancedSessionsLibrary::GetNumberOfNetworkPlayers(UObject* WorldContextObject, int32 &NumNetPlayers) +{ + //Get World + UWorld* TheWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + + if (!TheWorld) + { + UE_LOG(AdvancedSessionsLog, Warning, TEXT("GetNumberOfNetworkPlayers Failed to get World()!")); + return; + } + + NumNetPlayers = TheWorld->GetGameState()->PlayerArray.Num(); +} + +bool UAdvancedSessionsLibrary::ServerTravel(UObject* WorldContextObject, const FString& FURL, bool bAbsolute, bool bShouldSkipGameNotify) +{ + if (!WorldContextObject) + { + return false; + } + + //using a context object to get the world + UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if (World) + { + return World->ServerTravel(FURL, bAbsolute, bShouldSkipGameNotify); + } + + return false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedVoiceLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedVoiceLibrary.cpp new file mode 100644 index 0000000..f491524 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AdvancedVoiceLibrary.cpp @@ -0,0 +1,254 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedVoiceLibrary.h" + + +//General Log +DEFINE_LOG_CATEGORY(AdvancedVoiceLog); + +void UAdvancedVoiceLibrary::IsHeadsetPresent(bool & bHasHeadset, uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + bHasHeadset = false; + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Check For Headset couldn't get the voice interface!")); + return; + } + + bHasHeadset = VoiceInterface->IsHeadsetPresent(LocalPlayerNum); +} + +void UAdvancedVoiceLibrary::StartNetworkedVoice(uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Start Networked Voice couldn't get the voice interface!")); + return; + } + + VoiceInterface->StartNetworkedVoice(LocalPlayerNum); +} + +void UAdvancedVoiceLibrary::StopNetworkedVoice(uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Start Networked Voice couldn't get the voice interface!")); + return; + } + + VoiceInterface->StopNetworkedVoice(LocalPlayerNum); +} + +bool UAdvancedVoiceLibrary::RegisterLocalTalker(uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Register Local Talker couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->RegisterLocalTalker(LocalPlayerNum); +} + +void UAdvancedVoiceLibrary::RegisterAllLocalTalkers() +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Register Local Talkers couldn't get the voice interface!")); + return; + } + + VoiceInterface->RegisterLocalTalkers(); +} + + +void UAdvancedVoiceLibrary::UnRegisterLocalTalker(uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Unregister Local Talker couldn't get the voice interface!")); + return; + } + + VoiceInterface->UnregisterLocalTalker(LocalPlayerNum); +} + +void UAdvancedVoiceLibrary::UnRegisterAllLocalTalkers() +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("UnRegister All Local Talkers couldn't get the voice interface!")); + return; + } + + VoiceInterface->UnregisterLocalTalkers(); +} + +bool UAdvancedVoiceLibrary::RegisterRemoteTalker(const FBPUniqueNetId& UniqueNetId) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Register Remote Talker was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Register Remote Talker couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->RegisterRemoteTalker(*UniqueNetId.GetUniqueNetId()); +} + +bool UAdvancedVoiceLibrary::UnRegisterRemoteTalker(const FBPUniqueNetId& UniqueNetId) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("UnRegister Remote Talker was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("UnRegister Remote Talker couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->UnregisterRemoteTalker(*UniqueNetId.GetUniqueNetId()); +} + +void UAdvancedVoiceLibrary::RemoveAllRemoteTalkers() +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Remove All Remote Talkers couldn't get the voice interface!")); + return; + } + + VoiceInterface->RemoveAllRemoteTalkers(); +} + +bool UAdvancedVoiceLibrary::IsLocalPlayerTalking(uint8 LocalPlayerNum) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Is Local Player Talking couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->IsLocalPlayerTalking(LocalPlayerNum); +} + +bool UAdvancedVoiceLibrary::IsRemotePlayerTalking(const FBPUniqueNetId& UniqueNetId) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Is Remote Player Talking was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Is Remote Player Talking couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->IsRemotePlayerTalking(*UniqueNetId.GetUniqueNetId()); +} + +bool UAdvancedVoiceLibrary::IsPlayerMuted(uint8 LocalUserNumChecking, const FBPUniqueNetId& UniqueNetId) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Is Player Muted was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Is Player Muted couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->IsMuted(LocalUserNumChecking, *UniqueNetId.GetUniqueNetId()); +} + +bool UAdvancedVoiceLibrary::MuteRemoteTalker(uint8 LocalUserNum, const FBPUniqueNetId& UniqueNetId, bool bIsSystemWide) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Mute Remote Talker was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Mute Remote Talker couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->MuteRemoteTalker(LocalUserNum, *UniqueNetId.GetUniqueNetId(), bIsSystemWide); +} + +bool UAdvancedVoiceLibrary::UnMuteRemoteTalker(uint8 LocalUserNum, const FBPUniqueNetId& UniqueNetId, bool bIsSystemWide) +{ + if (!UniqueNetId.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Unmute Remote Talker was passed an invalid unique net id!")); + return false; + } + + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Unmute Remote Talker couldn't get the voice interface!")); + return false; + } + + return VoiceInterface->UnmuteRemoteTalker(LocalUserNum, *UniqueNetId.GetUniqueNetId(), bIsSystemWide); +} + + +void UAdvancedVoiceLibrary::GetNumLocalTalkers(int32 & NumLocalTalkers) +{ + IOnlineVoicePtr VoiceInterface = Online::GetVoiceInterface(); + + if (!VoiceInterface.IsValid()) + { + NumLocalTalkers = 0; + UE_LOG(AdvancedVoiceLog, Warning, TEXT("Unmute Remote Talker couldn't get the voice interface!")); + return; + } + + NumLocalTalkers = VoiceInterface->GetNumLocalTalkers(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AutoLoginUserCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AutoLoginUserCallbackProxy.cpp new file mode 100644 index 0000000..0c29769 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/AutoLoginUserCallbackProxy.cpp @@ -0,0 +1,76 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "AutoLoginUserCallbackProxy.h" +#include "Kismet/GameplayStatics.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// ULoginUserCallbackProxy + +UAutoLoginUserCallbackProxy::UAutoLoginUserCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnLoginCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) +{ +} + +UAutoLoginUserCallbackProxy* UAutoLoginUserCallbackProxy::AutoLoginUser(UObject* WorldContextObject, int32 LocalUserNum) +{ + UAutoLoginUserCallbackProxy* Proxy = NewObject(); + Proxy->LocalUserNumber = LocalUserNum; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UAutoLoginUserCallbackProxy::Activate() +{ + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + DelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(LocalUserNumber, Delegate); + Identity->AutoLogin(LocalUserNumber); + return; + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UAutoLoginUserCallbackProxy::OnCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& ErrorVal) +{ + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + Identity->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, DelegateHandle); + } + + if(APlayerController* PController = UGameplayStatics::GetPlayerController(WorldContextObject->GetWorld(), LocalUserNum)) + { + ULocalPlayer* Player = Cast(PController->Player); + + FUniqueNetIdRepl uniqueId(UserId.AsShared()); + + if (Player) + { + Player->SetCachedUniqueNetId(uniqueId); + } + + if (APlayerState* State = PController->PlayerState) + { + // Update UniqueId. See also ShowLoginUICallbackProxy.cpp + State->SetUniqueId(uniqueId); + } + } + + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CancelFindSessionsCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CancelFindSessionsCallbackProxy.cpp new file mode 100644 index 0000000..7d29f5f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CancelFindSessionsCallbackProxy.cpp @@ -0,0 +1,70 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "CancelFindSessionsCallbackProxy.h" + + +////////////////////////////////////////////////////////////////////////// +// UCancelFindSessionsCallbackProxy + +UCancelFindSessionsCallbackProxy::UCancelFindSessionsCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnCancelFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) +{ +} + +UCancelFindSessionsCallbackProxy* UCancelFindSessionsCallbackProxy::CancelFindSessions(UObject* WorldContextObject, class APlayerController* PlayerController) +{ + UCancelFindSessionsCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UCancelFindSessionsCallbackProxy::Activate() +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("CancelFindSessions"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + DelegateHandle = Sessions->AddOnCancelFindSessionsCompleteDelegate_Handle(Delegate); + Sessions->CancelFindSessions(); + + // OnCompleted will get called, nothing more to do now + return; + } + else + { + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UCancelFindSessionsCallbackProxy::OnCompleted(bool bWasSuccessful) +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("CancelFindSessionsCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnCancelFindSessionsCompleteDelegate_Handle(DelegateHandle); + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CreateSessionCallbackProxyAdvanced.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CreateSessionCallbackProxyAdvanced.cpp new file mode 100644 index 0000000..71a7d85 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/CreateSessionCallbackProxyAdvanced.cpp @@ -0,0 +1,182 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "CreateSessionCallbackProxyAdvanced.h" + + +////////////////////////////////////////////////////////////////////////// +// UCreateSessionCallbackProxyAdvanced + +UCreateSessionCallbackProxyAdvanced::UCreateSessionCallbackProxyAdvanced(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CreateCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateCompleted)) + , StartCompleteDelegate(FOnStartSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnStartCompleted)) + , NumPublicConnections(1) +{ +} + +UCreateSessionCallbackProxyAdvanced* UCreateSessionCallbackProxyAdvanced::CreateAdvancedSession(UObject* WorldContextObject, const TArray& ExtraSettings, class APlayerController* PlayerController, int32 PublicConnections, int32 PrivateConnections, bool bUseLAN, bool bAllowInvites, bool bIsDedicatedServer, bool bUsePresence, bool bUseLobbiesIfAvailable, bool bAllowJoinViaPresence, bool bAllowJoinViaPresenceFriendsOnly, bool bAntiCheatProtected, bool bUsesStats, bool bShouldAdvertise, bool bUseLobbiesVoiceChatIfAvailable, bool bStartAfterCreate) +{ + UCreateSessionCallbackProxyAdvanced* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->NumPublicConnections = PublicConnections; + Proxy->NumPrivateConnections = PrivateConnections; + Proxy->bUseLAN = bUseLAN; + Proxy->WorldContextObject = WorldContextObject; + Proxy->bAllowInvites = bAllowInvites; + Proxy->ExtraSettings = ExtraSettings; + Proxy->bDedicatedServer = bIsDedicatedServer; + Proxy->bUsePresence = bUsePresence; + Proxy->bUseLobbiesIfAvailable = bUseLobbiesIfAvailable; + Proxy->bAllowJoinViaPresence = bAllowJoinViaPresence; + Proxy->bAllowJoinViaPresenceFriendsOnly = bAllowJoinViaPresenceFriendsOnly; + Proxy->bAntiCheatProtected = bAntiCheatProtected; + Proxy->bUsesStats = bUsesStats; + Proxy->bShouldAdvertise = bShouldAdvertise; + Proxy->bUseLobbiesVoiceChatIfAvailable = bUseLobbiesVoiceChatIfAvailable; + Proxy->bStartAfterCreate = bStartAfterCreate; + return Proxy; +} + +void UCreateSessionCallbackProxyAdvanced::Activate() +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("CreateSession"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + + if (PlayerControllerWeakPtr.IsValid() ) + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.OnlineSub != nullptr) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + CreateCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(CreateCompleteDelegate); + + FOnlineSessionSettings Settings; + Settings.NumPublicConnections = NumPublicConnections; + Settings.NumPrivateConnections = NumPrivateConnections; + Settings.bShouldAdvertise = bShouldAdvertise; + Settings.bAllowJoinInProgress = true; + Settings.bIsLANMatch = bUseLAN; + Settings.bAllowJoinViaPresence = bAllowJoinViaPresence; + Settings.bIsDedicated = bDedicatedServer; + + if (bDedicatedServer) + { + Settings.bUsesPresence = false; + Settings.bUseLobbiesIfAvailable = false; + } + else + { + Settings.bUsesPresence = bUsePresence; + Settings.bUseLobbiesIfAvailable = bUseLobbiesIfAvailable; + } + + Settings.bUseLobbiesVoiceChatIfAvailable = bUseLobbiesIfAvailable ? bUseLobbiesVoiceChatIfAvailable : false; + Settings.bAllowJoinViaPresenceFriendsOnly = bAllowJoinViaPresenceFriendsOnly; + Settings.bAntiCheatProtected = bAntiCheatProtected; + Settings.bUsesStats = bUsesStats; + + // These are about the only changes over the standard Create Sessions Node + Settings.bAllowInvites = bAllowInvites; + + FOnlineSessionSetting ExtraSetting; + for (int i = 0; i < ExtraSettings.Num(); i++) + { + ExtraSetting.Data = ExtraSettings[i].Data; + // ViaOnlineServiceAndPing + ExtraSetting.AdvertisementType = EOnlineDataAdvertisementType::ViaOnlineService; + Settings.Settings.Add(ExtraSettings[i].Key, ExtraSetting); + } + + + if (!bDedicatedServer ) + { + if (PlayerControllerWeakPtr.IsValid() && Helper.UserID.IsValid()) + { + Sessions->CreateSession(*Helper.UserID, NAME_GameSession, Settings); + } + else + { + FFrame::KismetExecutionMessage(TEXT("Invalid Player controller when attempting to start a session"), ELogVerbosity::Warning); + Sessions->ClearOnCreateSessionCompleteDelegate_Handle(CreateCompleteDelegateHandle); + + // Fail immediately + OnFailure.Broadcast(); + } + } + else + Sessions->CreateSession(0, NAME_GameSession, Settings); + + // OnCreateCompleted will get called, nothing more to do now + return; + } + else + { + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UCreateSessionCallbackProxyAdvanced::OnCreateCompleted(FName SessionName, bool bWasSuccessful) +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("CreateSessionCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + //Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.OnlineSub != nullptr) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnCreateSessionCompleteDelegate_Handle(CreateCompleteDelegateHandle); + + if (bWasSuccessful) + { + if (this->bStartAfterCreate) + { + UE_LOG_ONLINE_SESSION(Display, TEXT("Session creation completed. Automatic start is turned on, starting session now.")); + StartCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(StartCompleteDelegate); + Sessions->StartSession(NAME_GameSession); // We'll call `OnSuccess.Broadcast()` when start succeeds. + } + else + { + UE_LOG_ONLINE_SESSION(Display, TEXT("Session creation completed. Automatic start is turned off, to start the session call 'StartSession'.")); + OnSuccess.Broadcast(); + } + + // OnStartCompleted will get called, nothing more to do now + return; + } + } + } + + if (!bWasSuccessful) + { + OnFailure.Broadcast(); + } +} + +void UCreateSessionCallbackProxyAdvanced::OnStartCompleted(FName SessionName, bool bWasSuccessful) +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("StartSessionCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + //Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.OnlineSub != nullptr) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnStartSessionCompleteDelegate_Handle(StartCompleteDelegateHandle); + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/EndSessionCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/EndSessionCallbackProxy.cpp new file mode 100644 index 0000000..0276399 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/EndSessionCallbackProxy.cpp @@ -0,0 +1,78 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "EndSessionCallbackProxy.h" + + +////////////////////////////////////////////////////////////////////////// +// UEndSessionCallbackProxy + +UEndSessionCallbackProxy::UEndSessionCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnEndSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) +{ +} + +UEndSessionCallbackProxy* UEndSessionCallbackProxy::EndSession(UObject* WorldContextObject, class APlayerController* PlayerController) +{ + UEndSessionCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UEndSessionCallbackProxy::Activate() +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("EndSession"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + FNamedOnlineSession* Session = Sessions->GetNamedSession(NAME_GameSession); + if (Session && + Session->SessionState == EOnlineSessionState::InProgress) + { + DelegateHandle = Sessions->AddOnEndSessionCompleteDelegate_Handle(Delegate); + Sessions->EndSession(NAME_GameSession); + } + else + { + OnSuccess.Broadcast(); + } + // OnCompleted will get called, nothing more to do now + return; + } + else + { + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UEndSessionCallbackProxy::OnCompleted(FName SessionName, bool bWasSuccessful) +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("EndSessionCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnEndSessionCompleteDelegate_Handle(DelegateHandle); + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindFriendSessionCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindFriendSessionCallbackProxy.cpp new file mode 100644 index 0000000..79b25de --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindFriendSessionCallbackProxy.cpp @@ -0,0 +1,107 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "FindFriendSessionCallbackProxy.h" + + +////////////////////////////////////////////////////////////////////////// +// UGetRecentPlayersCallbackProxy +DEFINE_LOG_CATEGORY(AdvancedFindFriendSessionLog); + +UFindFriendSessionCallbackProxy::UFindFriendSessionCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , OnFindFriendSessionCompleteDelegate(FOnFindFriendSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnFindFriendSessionCompleted)) +{ +} + +UFindFriendSessionCallbackProxy* UFindFriendSessionCallbackProxy::FindFriendSession(UObject* WorldContextObject, APlayerController *PlayerController, const FBPUniqueNetId &FriendUniqueNetId) +{ + UFindFriendSessionCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->cUniqueNetId = FriendUniqueNetId; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UFindFriendSessionCallbackProxy::Activate() +{ + if (!cUniqueNetId.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedFindFriendSessionLog, Warning, TEXT("FindFriendSession Failed received a bad UniqueNetId!")); + TArray EmptyResult; + OnFailure.Broadcast(EmptyResult); + return; + } + + if (!PlayerControllerWeakPtr.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedFindFriendSessionLog, Warning, TEXT("FindFriendSession Failed received a bad playercontroller!")); + TArray EmptyResult; + OnFailure.Broadcast(EmptyResult); + return; + } + + IOnlineSessionPtr Sessions = Online::GetSessionInterface(GetWorld()); + + if (Sessions.IsValid()) + { + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + if (!Player) + { + // Fail immediately + UE_LOG(AdvancedFindFriendSessionLog, Warning, TEXT("FindFriendSession Failed couldn't cast to ULocalPlayer!")); + TArray EmptyResult; + OnFailure.Broadcast(EmptyResult); + return; + } + + FindFriendSessionCompleteDelegateHandle = Sessions->AddOnFindFriendSessionCompleteDelegate_Handle(Player->GetControllerId(), OnFindFriendSessionCompleteDelegate); + + Sessions->FindFriendSession(Player->GetControllerId(), *cUniqueNetId.GetUniqueNetId()); + + return; + } + + // Fail immediately + TArray EmptyResult; + OnFailure.Broadcast(EmptyResult); +} + + +void UFindFriendSessionCallbackProxy::OnFindFriendSessionCompleted(int32 LocalPlayer, bool bWasSuccessful, const TArray& SessionInfo) +{ + IOnlineSessionPtr Sessions = Online::GetSessionInterface(GetWorld()); + + if (Sessions.IsValid()) + Sessions->ClearOnFindFriendSessionCompleteDelegate_Handle(LocalPlayer, FindFriendSessionCompleteDelegateHandle); + + if ( bWasSuccessful ) + { + TArray Result; + + for (auto& Sesh : SessionInfo) + { + if (Sesh.IsValid()) + { + FBlueprintSessionResult BSesh; + BSesh.OnlineResult = Sesh; + Result.Add(BSesh); + } + } + + if(Result.Num() > 0) + OnSuccess.Broadcast(Result); + else + { + UE_LOG(AdvancedFindFriendSessionLog, Warning, TEXT("FindFriendSession Failed, returned an invalid session.")); + OnFailure.Broadcast(Result); + } + } + else + { + UE_LOG(AdvancedFindFriendSessionLog, Warning, TEXT("FindFriendSession Failed")); + TArray EmptyResult; + OnFailure.Broadcast(EmptyResult); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindSessionsCallbackProxyAdvanced.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindSessionsCallbackProxyAdvanced.cpp new file mode 100644 index 0000000..56bb813 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/FindSessionsCallbackProxyAdvanced.cpp @@ -0,0 +1,437 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "FindSessionsCallbackProxyAdvanced.h" + +#include "Online/OnlineSessionNames.h" + +////////////////////////////////////////////////////////////////////////// +// UFindSessionsCallbackProxyAdvanced + + +UFindSessionsCallbackProxyAdvanced::UFindSessionsCallbackProxyAdvanced(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) + , bUseLAN(false) +{ + bRunSecondSearch = false; + bIsOnSecondSearch = false; +} + +UFindSessionsCallbackProxyAdvanced* UFindSessionsCallbackProxyAdvanced::FindSessionsAdvanced(UObject* WorldContextObject, class APlayerController* PlayerController, int MaxResults, bool bUseLAN, EBPServerPresenceSearchType ServerTypeToSearch, const TArray &Filters, bool bEmptyServersOnly, bool bNonEmptyServersOnly, bool bSecureServersOnly, bool bSearchLobbies, int MinSlotsAvailable) +{ + UFindSessionsCallbackProxyAdvanced* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->bUseLAN = bUseLAN; + Proxy->MaxResults = MaxResults; + Proxy->WorldContextObject = WorldContextObject; + Proxy->SearchSettings = Filters; + Proxy->ServerSearchType = ServerTypeToSearch; + Proxy->bEmptyServersOnly = bEmptyServersOnly, + Proxy->bNonEmptyServersOnly = bNonEmptyServersOnly; + Proxy->bSecureServersOnly = bSecureServersOnly; + Proxy->bSearchLobbies = bSearchLobbies; + Proxy->MinSlotsAvailable = MinSlotsAvailable; + return Proxy; +} + +void UFindSessionsCallbackProxyAdvanced::Activate() +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("FindSessions"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + // Re-initialize here, otherwise I think there might be issues with people re-calling search for some reason before it is destroyed + bRunSecondSearch = false; + bIsOnSecondSearch = false; + + DelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(Delegate); + + SearchObject = MakeShareable(new FOnlineSessionSearch); + SearchObject->MaxSearchResults = MaxResults; + SearchObject->bIsLanQuery = bUseLAN; + //SearchObject->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); + + // Create temp filter variable, because I had to re-define a blueprint version of this, it is required. + FOnlineSearchSettingsEx tem; + + /* // Search only for dedicated servers (value is true/false) + #define SEARCH_DEDICATED_ONLY FName(TEXT("DEDICATEDONLY")) + // Search for empty servers only (value is true/false) + #define SEARCH_EMPTY_SERVERS_ONLY FName(TEXT("EMPTYONLY")) + // Search for non empty servers only (value is true/false) + #define SEARCH_NONEMPTY_SERVERS_ONLY FName(TEXT("NONEMPTYONLY")) + // Search for secure servers only (value is true/false) + #define SEARCH_SECURE_SERVERS_ONLY FName(TEXT("SECUREONLY")) + // Search for presence sessions only (value is true/false) + #define SEARCH_PRESENCE FName(TEXT("PRESENCESEARCH")) + // Search for a match with min player availability (value is int) + #define SEARCH_MINSLOTSAVAILABLE FName(TEXT("MINSLOTSAVAILABLE")) + // Exclude all matches where any unique ids in a given array are present (value is string of the form "uniqueid1;uniqueid2;uniqueid3") + #define SEARCH_EXCLUDE_UNIQUEIDS FName(TEXT("EXCLUDEUNIQUEIDS")) + // User ID to search for session of + #define SEARCH_USER FName(TEXT("SEARCHUSER")) + // Keywords to match in session search + #define SEARCH_KEYWORDS FName(TEXT("SEARCHKEYWORDS"))*/ + /** Keywords to match in session search */ + /** The matchmaking queue name to matchmake in, e.g. "TeamDeathmatch" (value is string) */ + /** #define SEARCH_MATCHMAKING_QUEUE FName(TEXT("MATCHMAKINGQUEUE"))*/ + /** If set, use the named Xbox Live hopper to find a session via matchmaking (value is a string) */ + /** #define SEARCH_XBOX_LIVE_HOPPER_NAME FName(TEXT("LIVEHOPPERNAME"))*/ + /** Which session template from the service configuration to use */ + /** #define SEARCH_XBOX_LIVE_SESSION_TEMPLATE_NAME FName(TEXT("LIVESESSIONTEMPLATE"))*/ + /** Selection method used to determine which match to join when multiple are returned (valid only on Switch) */ + /** #define SEARCH_SWITCH_SELECTION_METHOD FName(TEXT("SWITCHSELECTIONMETHOD"))*/ + /** Whether to use lobbies vs sessions */ + /** #define SEARCH_LOBBIES FName(TEXT("LOBBYSEARCH"))*/ + + if (bEmptyServersOnly) + tem.Set(SEARCH_EMPTY_SERVERS_ONLY, true, EOnlineComparisonOp::Equals); + + if (bNonEmptyServersOnly) + tem.Set(SEARCH_NONEMPTY_SERVERS_ONLY, true, EOnlineComparisonOp::Equals); + + if (bSecureServersOnly) + tem.Set(SEARCH_SECURE_SERVERS_ONLY, true, EOnlineComparisonOp::Equals); + + if (MinSlotsAvailable != 0) + tem.Set(SEARCH_MINSLOTSAVAILABLE, MinSlotsAvailable, EOnlineComparisonOp::GreaterThanEquals); + + // Filter results + if (SearchSettings.Num() > 0) + { + for (int i = 0; i < SearchSettings.Num(); i++) + { + // Function that was added to make directly adding a FVariant possible + tem.HardSet(SearchSettings[i].PropertyKeyPair.Key, SearchSettings[i].PropertyKeyPair.Data, SearchSettings[i].ComparisonOp); + } + } + + switch (ServerSearchType) + { + + case EBPServerPresenceSearchType::ClientServersOnly: + { + tem.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); + + if (bSearchLobbies) + tem.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals); + } + break; + + case EBPServerPresenceSearchType::DedicatedServersOnly: + { + //tem.Set(SEARCH_DEDICATED_ONLY, true, EOnlineComparisonOp::Equals); + } + break; + + case EBPServerPresenceSearchType::AllServers: + default: + { + //if (IOnlineSubsystem::DoesInstanceExist("STEAM")) + //{ + bRunSecondSearch = true; + + SearchObjectDedicated = MakeShareable(new FOnlineSessionSearch); + SearchObjectDedicated->MaxSearchResults = MaxResults; + SearchObjectDedicated->bIsLanQuery = bUseLAN; + + FOnlineSearchSettingsEx DedicatedOnly = tem; + + tem.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); + + if (bSearchLobbies) + tem.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals); + + //DedicatedOnly.Set(SEARCH_DEDICATED_ONLY, true, EOnlineComparisonOp::Equals); + SearchObjectDedicated->QuerySettings = DedicatedOnly; + //} + } + break; + } + + // Copy the derived temp variable over to it's base class + SearchObject->QuerySettings = tem; + + Sessions->FindSessions(*Helper.UserID, SearchObject.ToSharedRef()); + + // OnQueryCompleted will get called, nothing more to do now + return; + } + else + { + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + } + + // Fail immediately + OnFailure.Broadcast(SessionSearchResults); +} + +void UFindSessionsCallbackProxyAdvanced::OnCompleted(bool bSuccess) +{ + FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("FindSessionsCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + Helper.QueryIDFromPlayerController(PlayerControllerWeakPtr.Get()); + + if (!bRunSecondSearch && Helper.IsValid()) + { + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnFindSessionsCompleteDelegate_Handle(DelegateHandle); + } + } + + if (bSuccess) + { + if (bIsOnSecondSearch) + { + if (SearchObjectDedicated.IsValid()) + { + // Just log the results for now, will need to add a blueprint-compatible search result struct + for (auto& Result : SearchObjectDedicated->SearchResults) + { + FString ResultText = FString::Printf(TEXT("Found a session. Ping is %d"), Result.PingInMs); + + FFrame::KismetExecutionMessage(*ResultText, ELogVerbosity::Log); + + FBlueprintSessionResult BPResult; + BPResult.OnlineResult = Result; + SessionSearchResults.AddUnique(BPResult); + } + OnSuccess.Broadcast(SessionSearchResults); + return; + } + } + else + { + if (SearchObject.IsValid()) + { + // Just log the results for now, will need to add a blueprint-compatible search result struct + for (auto& Result : SearchObject->SearchResults) + { + FString ResultText = FString::Printf(TEXT("Found a session. Ping is %d"), Result.PingInMs); + + FFrame::KismetExecutionMessage(*ResultText, ELogVerbosity::Log); + + FBlueprintSessionResult BPResult; + BPResult.OnlineResult = Result; + SessionSearchResults.AddUnique(BPResult); + } + if (!bRunSecondSearch) + { + OnSuccess.Broadcast(SessionSearchResults); + return; + } + } + } + } + else + { + if (!bRunSecondSearch) + { + // Need to account for only one of the searches failing + if (SessionSearchResults.Num() > 0) + OnSuccess.Broadcast(SessionSearchResults); + else + OnFailure.Broadcast(SessionSearchResults); + return; + } + } + + if (Helper.IsValid() && bRunSecondSearch && ServerSearchType == EBPServerPresenceSearchType::AllServers) + { + bRunSecondSearch = false; + bIsOnSecondSearch = true; + auto Sessions = Helper.OnlineSub->GetSessionInterface(); + Sessions->FindSessions(*Helper.UserID, SearchObjectDedicated.ToSharedRef()); + } + else // We lost our player controller + { + if (bSuccess && SessionSearchResults.Num() > 0) + OnSuccess.Broadcast(SessionSearchResults); + else + OnFailure.Broadcast(SessionSearchResults); + } +} + + +void UFindSessionsCallbackProxyAdvanced::FilterSessionResults(const TArray &SessionResults, const TArray &Filters, TArray &FilteredResults) +{ + for (int j = 0; j < SessionResults.Num(); j++) + { + bool bAddResult = true; + + // Filter results + if (Filters.Num() > 0) + { + const FOnlineSessionSetting * setting; + for (int i = 0; i < Filters.Num(); i++) + { + setting = SessionResults[j].OnlineResult.Session.SessionSettings.Settings.Find(Filters[i].PropertyKeyPair.Key); + + // Couldn't find this key + if (!setting) + continue; + + if (!CompareVariants(setting->Data, Filters[i].PropertyKeyPair.Data, Filters[i].ComparisonOp)) + { + bAddResult = false; + break; + } + } + } + + if (bAddResult) + FilteredResults.Add(SessionResults[j]); + } + + return; +} + + +bool UFindSessionsCallbackProxyAdvanced::CompareVariants(const FVariantData &A, const FVariantData &B, EOnlineComparisonOpRedux Comparator) +{ + if (A.GetType() != B.GetType()) + return false; + + switch (A.GetType()) + { + case EOnlineKeyValuePairDataType::Bool: + { + bool bA, bB; + A.GetValue(bA); + B.GetValue(bB); + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + default: + return false;break; + } + } + case EOnlineKeyValuePairDataType::Double: + { + double bA, bB; + A.GetValue(bA); + B.GetValue(bB); + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + case EOnlineComparisonOpRedux::GreaterThanEquals: + return (bA == bB || bA > bB); break; + case EOnlineComparisonOpRedux::LessThanEquals: + return (bA == bB || bA < bB); break; + case EOnlineComparisonOpRedux::GreaterThan: + return bA > bB; break; + case EOnlineComparisonOpRedux::LessThan: + return bA < bB; break; + default: + return false; break; + } + } + case EOnlineKeyValuePairDataType::Float: + { + float tbA, tbB; + double bA, bB; + A.GetValue(tbA); + B.GetValue(tbB); + bA = (double)tbA; + bB = (double)tbB; + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + case EOnlineComparisonOpRedux::GreaterThanEquals: + return (bA == bB || bA > bB); break; + case EOnlineComparisonOpRedux::LessThanEquals: + return (bA == bB || bA < bB); break; + case EOnlineComparisonOpRedux::GreaterThan: + return bA > bB; break; + case EOnlineComparisonOpRedux::LessThan: + return bA < bB; break; + default: + return false; break; + } + } + case EOnlineKeyValuePairDataType::Int32: + { + int32 bA, bB; + A.GetValue(bA); + B.GetValue(bB); + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + case EOnlineComparisonOpRedux::GreaterThanEquals: + return (bA == bB || bA > bB); break; + case EOnlineComparisonOpRedux::LessThanEquals: + return (bA == bB || bA < bB); break; + case EOnlineComparisonOpRedux::GreaterThan: + return bA > bB; break; + case EOnlineComparisonOpRedux::LessThan: + return bA < bB; break; + default: + return false; break; + } + } + case EOnlineKeyValuePairDataType::Int64: + { + uint64 bA, bB; + A.GetValue(bA); + B.GetValue(bB); + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + case EOnlineComparisonOpRedux::GreaterThanEquals: + return (bA == bB || bA > bB); break; + case EOnlineComparisonOpRedux::LessThanEquals: + return (bA == bB || bA < bB); break; + case EOnlineComparisonOpRedux::GreaterThan: + return bA > bB; break; + case EOnlineComparisonOpRedux::LessThan: + return bA < bB; break; + default: + return false; break; + } + } + + case EOnlineKeyValuePairDataType::String: + { + FString bA, bB; + A.GetValue(bA); + B.GetValue(bB); + switch (Comparator) + { + case EOnlineComparisonOpRedux::Equals: + return bA == bB; break; + case EOnlineComparisonOpRedux::NotEquals: + return bA != bB; break; + default: + return false; break; + } + } + + case EOnlineKeyValuePairDataType::Empty: + case EOnlineKeyValuePairDataType::Blob: + default: + return false; break; + } + + + +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetFriendsCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetFriendsCallbackProxy.cpp new file mode 100644 index 0000000..80fc481 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetFriendsCallbackProxy.cpp @@ -0,0 +1,96 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "GetFriendsCallbackProxy.h" + +#include "Online.h" +#include "Interfaces/OnlinePresenceInterface.h" + +////////////////////////////////////////////////////////////////////////// +// UGetFriendsCallbackProxy +DEFINE_LOG_CATEGORY(AdvancedGetFriendsLog); + +UGetFriendsCallbackProxy::UGetFriendsCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , FriendListReadCompleteDelegate(FOnReadFriendsListComplete::CreateUObject(this, &ThisClass::OnReadFriendsListCompleted)) +{ +} + +UGetFriendsCallbackProxy* UGetFriendsCallbackProxy::GetAndStoreFriendsList(UObject* WorldContextObject, class APlayerController* PlayerController) +{ + UGetFriendsCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UGetFriendsCallbackProxy::Activate() +{ + if (!PlayerControllerWeakPtr.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedGetFriendsLog, Warning, TEXT("GetFriends Failed received a bad player controller!")); + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); + return; + } + + IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + { + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + Friends->ReadFriendsList(Player->GetControllerId(), EFriendsLists::ToString((EFriendsLists::Default)), FriendListReadCompleteDelegate); + return; + } + + // Fail immediately + TArray EmptyArray; + + OnFailure.Broadcast(EmptyArray); +} + +void UGetFriendsCallbackProxy::OnReadFriendsListCompleted(int32 LocalUserNum, bool bWasSuccessful, const FString& ListName, const FString& ErrorString) +{ + if (bWasSuccessful) + { + IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + { + // Not actually needed anymore, plus was not being validated and causing a crash + //ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + TArray FriendsListOut; + TArray< TSharedRef > FriendList; + Friends->GetFriendsList(LocalUserNum, ListName, FriendList); + + for (int32 i = 0; i < FriendList.Num(); i++) + { + TSharedRef Friend = FriendList[i]; + FBPFriendInfo BPF; + FOnlineUserPresence pres = Friend->GetPresence(); + BPF.OnlineState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + BPF.DisplayName = Friend->GetDisplayName(); + BPF.RealName = Friend->GetRealName(); + BPF.UniqueNetId.SetUniqueNetId(Friend->GetUserId()); + BPF.bIsPlayingSameGame = pres.bIsPlayingThisGame; + + BPF.PresenceInfo.bIsOnline = pres.bIsOnline; + BPF.PresenceInfo.bHasVoiceSupport = pres.bHasVoiceSupport; + BPF.PresenceInfo.bIsPlaying = pres.bIsPlaying; + BPF.PresenceInfo.PresenceState = ((EBPOnlinePresenceState)((int32)pres.Status.State)); + BPF.PresenceInfo.StatusString = pres.Status.StatusStr; + BPF.PresenceInfo.bIsJoinable = pres.bIsJoinable; + BPF.PresenceInfo.bIsPlayingThisGame = pres.bIsPlayingThisGame; + + + FriendsListOut.Add(BPF); + } + + OnSuccess.Broadcast(FriendsListOut); + } + } + else + { + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetRecentPlayersCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetRecentPlayersCallbackProxy.cpp new file mode 100644 index 0000000..8eb3a3d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetRecentPlayersCallbackProxy.cpp @@ -0,0 +1,86 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "GetRecentPlayersCallbackProxy.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// UGetRecentPlayersCallbackProxy +DEFINE_LOG_CATEGORY(AdvancedGetRecentPlayersLog); + +UGetRecentPlayersCallbackProxy::UGetRecentPlayersCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , QueryRecentPlayersCompleteDelegate(FOnQueryRecentPlayersCompleteDelegate::CreateUObject(this, &ThisClass::OnQueryRecentPlayersCompleted)) +{ +} + +UGetRecentPlayersCallbackProxy* UGetRecentPlayersCallbackProxy::GetAndStoreRecentPlayersList(UObject* WorldContextObject, const FBPUniqueNetId& UniqueNetId) +{ + UGetRecentPlayersCallbackProxy* Proxy = NewObject(); + Proxy->cUniqueNetId = UniqueNetId; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UGetRecentPlayersCallbackProxy::Activate() +{ + if (!cUniqueNetId.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedGetRecentPlayersLog, Warning, TEXT("GetRecentPlayers Failed received a bad UniqueNetId!")); + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); + return; + } + + IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + { + DelegateHandle = Friends->AddOnQueryRecentPlayersCompleteDelegate_Handle(QueryRecentPlayersCompleteDelegate); + + // Testing with null namespace + Friends->QueryRecentPlayers(*(cUniqueNetId.GetUniqueNetId()), ""); + return; + } + // Fail immediately + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); +} + +void UGetRecentPlayersCallbackProxy::OnQueryRecentPlayersCompleted(const FUniqueNetId &UserID, const FString &Namespace, bool bWasSuccessful, const FString& ErrorString) +{ + + IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + Friends->ClearOnQueryRecentPlayersCompleteDelegate_Handle(DelegateHandle); + + + if (bWasSuccessful) + { + // WHOOPS + //IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + { + TArray PlayersListOut; + TArray< TSharedRef > PlayerList; + + Friends->GetRecentPlayers(*(cUniqueNetId.GetUniqueNetId()), "", PlayerList); + + for (int32 i = 0; i < PlayerList.Num(); i++) + { + TSharedRef Player = PlayerList[i]; + FBPOnlineRecentPlayer BPF; + BPF.DisplayName = Player->GetDisplayName(); + BPF.RealName = Player->GetRealName(); + BPF.UniqueNetId.SetUniqueNetId(Player->GetUserId()); + PlayersListOut.Add(BPF); + } + + OnSuccess.Broadcast(PlayersListOut); + } + } + else + { + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetUserPrivilegeCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetUserPrivilegeCallbackProxy.cpp new file mode 100644 index 0000000..cec618f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/GetUserPrivilegeCallbackProxy.cpp @@ -0,0 +1,41 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "GetUserPrivilegeCallbackProxy.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// UGetUserPrivilegeCallbackProxy + +UGetUserPrivilegeCallbackProxy::UGetUserPrivilegeCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UGetUserPrivilegeCallbackProxy* UGetUserPrivilegeCallbackProxy::GetUserPrivilege(UObject* WorldContextObject, const EBPUserPrivileges & PrivilegeToCheck, const FBPUniqueNetId & PlayerUniqueNetID) +{ + UGetUserPrivilegeCallbackProxy* Proxy = NewObject(); + Proxy->PlayerUniqueNetID.SetUniqueNetId(PlayerUniqueNetID.GetUniqueNetId()); + Proxy->UserPrivilege = PrivilegeToCheck; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UGetUserPrivilegeCallbackProxy::Activate() +{ + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + Identity->GetUserPrivilege(*PlayerUniqueNetID.GetUniqueNetId(), (EUserPrivileges::Type)UserPrivilege, IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)); + return; + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UGetUserPrivilegeCallbackProxy::OnCompleted(const FUniqueNetId& PlayerID, EUserPrivileges::Type Privilege, uint32 PrivilegeResult) +{ + OnSuccess.Broadcast(/*PlayerID,*/ (EBPUserPrivileges)Privilege, PrivilegeResult == 0); +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LoginUserCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LoginUserCallbackProxy.cpp new file mode 100644 index 0000000..3bb211d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LoginUserCallbackProxy.cpp @@ -0,0 +1,97 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "LoginUserCallbackProxy.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// ULoginUserCallbackProxy + +ULoginUserCallbackProxy::ULoginUserCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnLoginCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) +{ +} + +ULoginUserCallbackProxy* ULoginUserCallbackProxy::LoginUser(UObject* WorldContextObject, class APlayerController* PlayerController, FString UserID, FString UserToken, FString AuthType) +{ + ULoginUserCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->UserID = UserID; + Proxy->UserToken = UserToken; + Proxy->AuthType = AuthType; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void ULoginUserCallbackProxy::Activate() +{ + + if (!PlayerControllerWeakPtr.IsValid()) + { + OnFailure.Broadcast(); + return; + } + + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + if (!Player) + { + OnFailure.Broadcast(); + return; + } + + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + // Fallback to default AuthType if nothing is specified + if (AuthType.IsEmpty()) + { + AuthType = Identity->GetAuthType(); + } + DelegateHandle = Identity->AddOnLoginCompleteDelegate_Handle(Player->GetControllerId(), Delegate); + FOnlineAccountCredentials AccountCreds(AuthType, UserID, UserToken); + Identity->Login(Player->GetControllerId(), AccountCreds); + return; + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void ULoginUserCallbackProxy::OnCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& ErrorVal) +{ + if (PlayerControllerWeakPtr.IsValid()) + { + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + FUniqueNetIdRepl UniqueID(UserId.AsShared()); + + if (Player) + { + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + Identity->ClearOnLoginCompleteDelegate_Handle(Player->GetControllerId(), DelegateHandle); + } + Player->SetCachedUniqueNetId(UniqueID); + } + + if (APlayerState* State = PlayerControllerWeakPtr->PlayerState) + { + // Update UniqueId. See also ShowLoginUICallbackProxy.cpp + State->SetUniqueId(UniqueID); + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LogoutUserCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LogoutUserCallbackProxy.cpp new file mode 100644 index 0000000..29b1b53 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/LogoutUserCallbackProxy.cpp @@ -0,0 +1,81 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "LogoutUserCallbackProxy.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// ULogoutUserCallbackProxy + +ULogoutUserCallbackProxy::ULogoutUserCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Delegate(FOnLogoutCompleteDelegate::CreateUObject(this, &ThisClass::OnCompleted)) +{ +} + +ULogoutUserCallbackProxy* ULogoutUserCallbackProxy::LogoutUser(UObject* WorldContextObject, class APlayerController* PlayerController) +{ + ULogoutUserCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void ULogoutUserCallbackProxy::Activate() +{ + + if (!PlayerControllerWeakPtr.IsValid()) + { + OnFailure.Broadcast(); + return; + } + + + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + if (!Player) + { + OnFailure.Broadcast(); + return; + } + + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + DelegateHandle = Identity->AddOnLogoutCompleteDelegate_Handle(Player->GetControllerId(), Delegate); + Identity->Logout(Player->GetControllerId()); + return; + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void ULogoutUserCallbackProxy::OnCompleted(int LocalUserNum, bool bWasSuccessful) +{ + + if (PlayerControllerWeakPtr.IsValid()) + { + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + if (Player) + { + auto Identity = Online::GetIdentityInterface(); + + if (Identity.IsValid()) + { + Identity->ClearOnLogoutCompleteDelegate_Handle(Player->GetControllerId(), DelegateHandle); + } + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/SendFriendInviteCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/SendFriendInviteCallbackProxy.cpp new file mode 100644 index 0000000..71fed1e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/SendFriendInviteCallbackProxy.cpp @@ -0,0 +1,74 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "SendFriendInviteCallbackProxy.h" + +#include "Online.h" + +////////////////////////////////////////////////////////////////////////// +// UGetRecentPlayersCallbackProxy +DEFINE_LOG_CATEGORY(AdvancedSendFriendInviteLog); + +USendFriendInviteCallbackProxy::USendFriendInviteCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , OnSendInviteCompleteDelegate(FOnSendInviteComplete::CreateUObject(this, &ThisClass::OnSendInviteComplete)) +{ +} + +USendFriendInviteCallbackProxy* USendFriendInviteCallbackProxy::SendFriendInvite(UObject* WorldContextObject, APlayerController *PlayerController, const FBPUniqueNetId &UniqueNetIDInvited) +{ + USendFriendInviteCallbackProxy* Proxy = NewObject(); + Proxy->PlayerControllerWeakPtr = PlayerController; + Proxy->cUniqueNetId = UniqueNetIDInvited; + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void USendFriendInviteCallbackProxy::Activate() +{ + if (!cUniqueNetId.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedSendFriendInviteLog, Warning, TEXT("SendFriendInvite Failed received a bad UniqueNetId!")); + OnFailure.Broadcast(); + return; + } + + if (!PlayerControllerWeakPtr.IsValid()) + { + // Fail immediately + UE_LOG(AdvancedSendFriendInviteLog, Warning, TEXT("SendFriendInvite Failed received a bad playercontroller!")); + OnFailure.Broadcast(); + return; + } + + IOnlineFriendsPtr Friends = Online::GetFriendsInterface(); + if (Friends.IsValid()) + { + ULocalPlayer* Player = Cast(PlayerControllerWeakPtr->Player); + + if (!Player) + { + // Fail immediately + UE_LOG(AdvancedSendFriendInviteLog, Warning, TEXT("SendFriendInvite Failed couldn't cast to ULocalPlayer!")); + OnFailure.Broadcast(); + return; + } + + Friends->SendInvite(Player->GetControllerId(), *cUniqueNetId.GetUniqueNetId(), EFriendsLists::ToString((EFriendsLists::Default)), OnSendInviteCompleteDelegate); + return; + } + // Fail immediately + OnFailure.Broadcast(); +} + +void USendFriendInviteCallbackProxy::OnSendInviteComplete(int32 LocalPlayerNum, bool bWasSuccessful, const FUniqueNetId &InvitedPlayer, const FString &ListName, const FString &ErrorString) +{ + if ( bWasSuccessful ) + { + OnSuccess.Broadcast(); + } + else + { + UE_LOG(AdvancedSendFriendInviteLog, Warning, TEXT("SendFriendInvite Failed with error: %s"), *ErrorString); + OnFailure.Broadcast(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/StartSessionCallbackProxyAdvanced.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/StartSessionCallbackProxyAdvanced.cpp new file mode 100644 index 0000000..3831384 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/StartSessionCallbackProxyAdvanced.cpp @@ -0,0 +1,62 @@ +#include "StartSessionCallbackProxyAdvanced.h" + +UStartSessionCallbackProxyAdvanced::UStartSessionCallbackProxyAdvanced(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , StartCompleteDelegate(FOnStartSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnStartCompleted)) +{ +} + +UStartSessionCallbackProxyAdvanced* UStartSessionCallbackProxyAdvanced::StartAdvancedSession( + const UObject* WorldContextObject) +{ + UStartSessionCallbackProxyAdvanced* Proxy = NewObject(); + Proxy->WorldContextObject = WorldContextObject; + return Proxy; +} + +void UStartSessionCallbackProxyAdvanced::Activate() +{ + const FOnlineSubsystemBPCallHelperAdvanced Helper( + TEXT("StartSession"), + GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + + if (Helper.OnlineSub != nullptr) + { + const auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + StartCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(StartCompleteDelegate); + Sessions->StartSession(NAME_GameSession); + return; + } + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + + // Fail immediately + OnFailure.Broadcast(); +} + +void UStartSessionCallbackProxyAdvanced::OnStartCompleted(FName SessionName, bool bWasSuccessful) +{ + const FOnlineSubsystemBPCallHelperAdvanced Helper( + TEXT("StartSessionCallback"), + GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + + if (Helper.OnlineSub != nullptr) + { + const auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnStartSessionCompleteDelegate_Handle(StartCompleteDelegateHandle); + } + } + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + } + else + { + OnFailure.Broadcast(); + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/UpdateSessionCallbackProxyAdvanced.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/UpdateSessionCallbackProxyAdvanced.cpp new file mode 100644 index 0000000..7fa1ea0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSessions/Source/AdvancedSessions/Private/UpdateSessionCallbackProxyAdvanced.cpp @@ -0,0 +1,130 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#include "UpdateSessionCallbackProxyAdvanced.h" + + +////////////////////////////////////////////////////////////////////////// +// UUpdateSessionCallbackProxyAdvanced + +UUpdateSessionCallbackProxyAdvanced::UUpdateSessionCallbackProxyAdvanced(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , OnUpdateSessionCompleteDelegate(FOnUpdateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnUpdateCompleted)) + , NumPublicConnections(1) +{ +} + +UUpdateSessionCallbackProxyAdvanced* UUpdateSessionCallbackProxyAdvanced::UpdateSession(UObject* WorldContextObject, const TArray &ExtraSettings, int32 PublicConnections, int32 PrivateConnections, bool bUseLAN, bool bAllowInvites, bool bAllowJoinInProgress, bool bRefreshOnlineData, bool bIsDedicatedServer, bool bShouldAdvertise) +{ + UUpdateSessionCallbackProxyAdvanced* Proxy = NewObject(); + Proxy->NumPublicConnections = PublicConnections; + Proxy->NumPrivateConnections = PrivateConnections; + Proxy->bUseLAN = bUseLAN; + Proxy->WorldContextObject = WorldContextObject; + Proxy->bAllowInvites = bAllowInvites; + Proxy->ExtraSettings = ExtraSettings; + Proxy->bRefreshOnlineData = bRefreshOnlineData; + Proxy->bAllowJoinInProgress = bAllowJoinInProgress; + Proxy->bDedicatedServer = bIsDedicatedServer; + Proxy->bShouldAdvertise = bShouldAdvertise; + return Proxy; +} + +void UUpdateSessionCallbackProxyAdvanced::Activate() +{ + const FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("UpdateSession"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + + if (Helper.OnlineSub != nullptr) + { + const auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + if (Sessions->GetNumSessions() < 1) + { + OnFailure.Broadcast(); + GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("NO REGISTERED SESSIONS!")); + return; + } + + // This gets the actual session itself + //FNamedOnlineSession * curSession = Sessions->GetNamedSession(NAME_GameSession); + FOnlineSessionSettings* Settings = Sessions->GetSessionSettings(NAME_GameSession); + + if (!Settings) + { + // Fail immediately + OnFailure.Broadcast(); + return; + } + + OnUpdateSessionCompleteDelegateHandle = Sessions->AddOnUpdateSessionCompleteDelegate_Handle(OnUpdateSessionCompleteDelegate); + + // FOnlineSessionSettings Settings; + //Settings->BuildUniqueId = GetBuildUniqueId(); + Settings->NumPublicConnections = NumPublicConnections; + Settings->NumPrivateConnections = NumPrivateConnections; + Settings->bShouldAdvertise = bShouldAdvertise; + Settings->bAllowJoinInProgress = bAllowJoinInProgress; + Settings->bIsLANMatch = bUseLAN; + //Settings->bUsesPresence = true; + //Settings->bAllowJoinViaPresence = true; + Settings->bAllowInvites = bAllowInvites; + Settings->bAllowJoinInProgress = bAllowJoinInProgress; + Settings->bIsDedicated = bDedicatedServer; + + FOnlineSessionSetting * fSetting = NULL; + FOnlineSessionSetting ExtraSetting; + for (int i = 0; i < ExtraSettings.Num(); i++) + { + fSetting = Settings->Settings.Find(ExtraSettings[i].Key); + + if (fSetting) + { + fSetting->Data = ExtraSettings[i].Data; + } + else + { + ExtraSetting.Data = ExtraSettings[i].Data; + ExtraSetting.AdvertisementType = EOnlineDataAdvertisementType::ViaOnlineService; + Settings->Settings.Add(ExtraSettings[i].Key, ExtraSetting); + } + } + + Sessions->UpdateSession(NAME_GameSession, *Settings, bRefreshOnlineData); + + // OnUpdateCompleted will get called, nothing more to do now + return; + } + else + { + FFrame::KismetExecutionMessage(TEXT("Sessions not supported by Online Subsystem"), ELogVerbosity::Warning); + } + } + // Fail immediately + OnFailure.Broadcast(); + GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Sessions not supported")); +} + +void UUpdateSessionCallbackProxyAdvanced::OnUpdateCompleted(FName SessionName, bool bWasSuccessful) +{ + const FOnlineSubsystemBPCallHelperAdvanced Helper(TEXT("UpdateSessionCallback"), GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)); + + if (Helper.OnlineSub != nullptr) + { + const auto Sessions = Helper.OnlineSub->GetSessionInterface(); + if (Sessions.IsValid()) + { + Sessions->ClearOnUpdateSessionCompleteDelegate_Handle(OnUpdateSessionCompleteDelegateHandle); + + if (bWasSuccessful) + { + OnSuccess.Broadcast(); + return; + } + } + } + + if (!bWasSuccessful) + { + OnFailure.Broadcast(); + GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("WAS NOT SUCCESSFUL")); + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/AdvancedSteamSessions.uplugin b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/AdvancedSteamSessions.uplugin new file mode 100644 index 0000000..fa63cb3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/AdvancedSteamSessions.uplugin @@ -0,0 +1,38 @@ +{ + "FileVersion" : 3, + + "FriendlyName" : "Advanced Steam Sessions", + "Version" : 5.1, + "VersionName": "5.1", + "Description" : "Adds new blueprint functions to handle more advanced session operations in Steam. REQUIRES ADVANCED SESSIONS", + "Category" : "Advanced Sessions Plugin", + "CreatedBy" : "Joshua Statzer", + "CreatedByURL" : "N/A", + + "Modules" : + [ + { + "Name" : "AdvancedSteamSessions", + "Type" : "RunTime", + "LoadingPhase" : "PreDefault" + } + ], + "Plugins": [ + { + "Name": "AdvancedSessions", + "Enabled": true + }, + { + "Name": "OnlineSubsystem", + "Enabled": true + }, + { + "Name": "OnlineSubsystemUtils", + "Enabled": true + }, + { + "Name": "SteamShared", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Config/FilterPlugin.ini b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[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 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Resources/Icon128.png b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Resources/Icon128.png new file mode 100644 index 0000000..fee08fc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eac09a19149b472680abd8bcb38513edbe29083a34edb4bbdbfbebec68540029 +size 5879 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/AdvancedSteamSessions.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/AdvancedSteamSessions.Build.cs new file mode 100644 index 0000000..609d7b5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/AdvancedSteamSessions.Build.cs @@ -0,0 +1,22 @@ +using UnrealBuildTool; +using System.IO; + +public class AdvancedSteamSessions : ModuleRules +{ + public AdvancedSteamSessions(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //bEnforceIWYU = true; + + PublicDefinitions.Add("WITH_ADVANCED_STEAM_SESSIONS=1"); + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "OnlineSubsystem", "CoreUObject", "OnlineSubsystemUtils", "Networking", "Sockets", "AdvancedSessions"/*"Voice", "OnlineSubsystemSteam"*/ }); + PrivateDependencyModuleNames.AddRange(new string[] { "OnlineSubsystem", "Sockets", "Networking", "OnlineSubsystemUtils" /*"Voice", "Steamworks","OnlineSubsystemSteam"*/}); + + if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Linux) || (Target.Platform == UnrealTargetPlatform.Mac)) + { + PublicDependencyModuleNames.AddRange(new string[] { "Steamworks"/*, "OnlineSubsystemSteam"*/ }); + //PublicIncludePaths.AddRange(new string[] { "../Plugins/Online/OnlineSubsystemSteam/Source/Private" });// This is dumb but it isn't very open + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamFriendsLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamFriendsLibrary.h new file mode 100644 index 0000000..7d8cead --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamFriendsLibrary.h @@ -0,0 +1,387 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#include "Interfaces/OnlineFriendsInterface.h" +#include "Interfaces/OnlineUserInterface.h" +#include "Interfaces/OnlineMessageInterface.h" +#include "Interfaces/OnlinePresenceInterface.h" +#include "Engine/GameInstance.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "BlueprintDataDefinitions.h" +#include "UObject/UObjectIterator.h" + +// This is taken directly from UE4 - OnlineSubsystemSteamPrivatePCH.h as a fix for the array_count macro +// @todo Steam: Steam headers trigger secure-C-runtime warnings in Visual C++. Rather than mess with _CRT_SECURE_NO_WARNINGS, we'll just +// disable the warnings locally. Remove when this is fixed in the SDK +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +// #TODO check back on this at some point +#pragma warning(disable:4265) // SteamAPI CCallback< specifically, this warning is off by default but 4.17 turned it on.... +#endif + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + +#pragma push_macro("ARRAY_COUNT") +#undef ARRAY_COUNT + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(push)) +MSVC_PRAGMA(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) +#endif // USING_CODE_ANALYSIS + +#include + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(pop)) +#endif // USING_CODE_ANALYSIS + +#include +#include +//#include +#pragma pop_macro("ARRAY_COUNT") + +// @todo Steam: See above +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// Making a copy of this here since the original is still in a private folder and is screwing with things +/** +* Steam specific implementation of the unique net id +*/ +class FUniqueNetIdSteam2 : + public FUniqueNetId +{ +PACKAGE_SCOPE: + /** Holds the net id for a player */ + uint64 UniqueNetId; + + /** Hidden on purpose */ + FUniqueNetIdSteam2() : + UniqueNetId(0) + { + } + + /** + * Copy Constructor + * + * @param Src the id to copy + */ + explicit FUniqueNetIdSteam2(const FUniqueNetIdSteam2& Src) : + UniqueNetId(Src.UniqueNetId) + { + } + +public: + /** + * Constructs this object with the specified net id + * + * @param InUniqueNetId the id to set ours to + */ + explicit FUniqueNetIdSteam2(uint64 InUniqueNetId) : + UniqueNetId(InUniqueNetId) + { + } + + /** + * Constructs this object with the steam id + * + * @param InUniqueNetId the id to set ours to + */ + explicit FUniqueNetIdSteam2(CSteamID InSteamId) : + UniqueNetId(InSteamId.ConvertToUint64()) + { + } + + /** + * Constructs this object with the specified net id + * + * @param String textual representation of an id + */ + explicit FUniqueNetIdSteam2(const FString& Str) : + UniqueNetId(FCString::Atoi64(*Str)) + { + } + + + /** + * Constructs this object with the specified net id + * + * @param InUniqueNetId the id to set ours to (assumed to be FUniqueNetIdSteam in fact) + */ + explicit FUniqueNetIdSteam2(const FUniqueNetId& InUniqueNetId) : + UniqueNetId(*(uint64*)InUniqueNetId.GetBytes()) + { + } + + virtual FName GetType() const override + { + return STEAM_SUBSYSTEM; + } + + /** + * Get the raw byte representation of this net id + * This data is platform dependent and shouldn't be manipulated directly + * + * @return byte array of size GetSize() + */ + virtual const uint8* GetBytes() const override + { + return (uint8*)&UniqueNetId; + } + + /** + * Get the size of the id + * + * @return size in bytes of the id representation + */ + virtual int32 GetSize() const override + { + return sizeof(uint64); + } + + /** + * Check the validity of the id + * + * @return true if this is a well formed ID, false otherwise + */ + virtual bool IsValid() const override + { + return UniqueNetId != 0 && CSteamID(UniqueNetId).IsValid(); + } + + /** + * Platform specific conversion to string representation of data + * + * @return data in string form + */ + virtual FString ToString() const override + { + return FString::Printf(TEXT("%llu"), UniqueNetId); + } + + /** + * Get a human readable representation of the net id + * Shouldn't be used for anything other than logging/debugging + * + * @return id in string form + */ + virtual FString ToDebugString() const override + { + CSteamID SteamID(UniqueNetId); + if (SteamID.IsLobby()) + { + return FString::Printf(TEXT("Lobby [0x%llX]"), UniqueNetId); + } + else if (SteamID.BAnonGameServerAccount()) + { + return FString::Printf(TEXT("Server [0x%llX]"), UniqueNetId); + } + else if (SteamID.IsValid()) + { + const FString NickName(SteamFriends() ? UTF8_TO_TCHAR(SteamFriends()->GetFriendPersonaName(UniqueNetId)) : TEXT("UNKNOWN")); + return FString::Printf(TEXT("%s [0x%llX]"), *NickName, UniqueNetId); + } + else + { + return FString::Printf(TEXT("INVALID [0x%llX]"), UniqueNetId); + } + } + + + virtual uint32 GetTypeHash() const override + { + return ::GetTypeHash(UniqueNetId); + } + + /** Convenience cast to CSteamID */ + operator CSteamID() + { + return UniqueNetId; + } + + /** Convenience cast to CSteamID */ + operator const CSteamID() const + { + return UniqueNetId; + } + + /** Convenience cast to CSteamID pointer */ + operator CSteamID*() + { + return (CSteamID*)&UniqueNetId; + } + + /** Convenience cast to CSteamID pointer */ + operator const CSteamID*() const + { + return (const CSteamID*)&UniqueNetId; + } + + friend FArchive& operator<<(FArchive& Ar, FUniqueNetIdSteam2& UserId) + { + return Ar << UserId.UniqueNetId; + } +}; + +#endif + +#include "AdvancedSteamFriendsLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedSteamFriendsLog, Log, All); + +UENUM(Blueprintable) +enum class SteamAvatarSize : uint8 +{ + SteamAvatar_INVALID = 0, + SteamAvatar_Small = 1, + SteamAvatar_Medium = 2, + SteamAvatar_Large = 3 +}; + +UENUM(Blueprintable) +enum class ESteamUserOverlayType : uint8 +{ + /*Opens the overlay web browser to the specified user or groups profile.*/ + steamid, + /*Opens a chat window to the specified user, or joins the group chat.*/ + chat, + /*Opens a window to a Steam Trading session that was started with the ISteamEconomy / StartTrade Web API.*/ + jointrade, + /*Opens the overlay web browser to the specified user's stats.*/ + stats, + /*Opens the overlay web browser to the specified user's achievements.*/ + achievements, + /*Opens the overlay in minimal mode prompting the user to add the target user as a friend.*/ + friendadd, + /*Opens the overlay in minimal mode prompting the user to remove the target friend.*/ + friendremove, + /*Opens the overlay in minimal mode prompting the user to accept an incoming friend invite.*/ + friendrequestaccept, + /*Opens the overlay in minimal mode prompting the user to ignore an incoming friend invite.*/ + friendrequestignore +}; + +static FString EnumToString(const FString& enumName, uint8 value) +{ + + const UEnum* EnumPtr = FindFirstObject(*enumName, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("EumtoString")); + + if (!EnumPtr) + return FString(); + + FString EnumName = EnumPtr->GetNameStringByIndex(value); + return EnumName; +} + + +USTRUCT(BlueprintType, Category = "Online|SteamAPI|SteamGroups") +struct FBPSteamGroupInfo +{ + GENERATED_USTRUCT_BODY() + +public: + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + FBPUniqueNetId GroupID; // Uint64 representation + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + FString GroupName; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + FString GroupTag; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + int32 numOnline = 0; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + int32 numInGame = 0; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + int32 numChatting = 0; + +}; + +UENUM(Blueprintable) +enum class EBPTextFilteringContext : uint8 +{ + /*Unknown context.*/ + FContext_Unknown = 0, + /*Game content, only legally required filtering is performed.*/ + FContext_GameContent = 1, + /*Char from another player.*/ + FContext_Chat = 2, + /*Character or item name.*/ + FContext_Name = 3 +}; + +UCLASS() +class UAdvancedSteamFriendsLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + //********* Friend List Functions *************// + + // Get a texture of a valid friends avatar, STEAM ONLY, Returns invalid texture if the subsystem hasn't loaded that size of avatar yet + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI", meta = (ExpandEnumAsExecs = "Result")) + static UTexture2D * GetSteamFriendAvatar(const FBPUniqueNetId UniqueNetId, EBlueprintAsyncResultSwitch &Result, SteamAvatarSize AvatarSize = SteamAvatarSize::SteamAvatar_Medium); + + // Preloads the avatar and name of a steam friend, return whether it is already available or not, STEAM ONLY, Takes time to actually load everything after this is called. + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI") + static bool RequestSteamFriendInfo(const FBPUniqueNetId UniqueNetId, bool bRequireNameOnly = false); + + // Opens the steam overlay to go to the specified user dialog + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI") + static bool OpenSteamUserOverlay(const FBPUniqueNetId UniqueNetId, ESteamUserOverlayType DialogType); + + // Returns if the steam overlay is currently active (this can return false during initial overlay hooking) + UFUNCTION(BlueprintPure, Category = "Online|AdvancedFriends|SteamAPI") + static bool IsOverlayEnabled(); + + // Gets the level of a friends steam account, STEAM ONLY, Returns -1 if the steam level is not known, might need RequestSteamFriendInfo called first. + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI") + static int32 GetFriendSteamLevel(const FBPUniqueNetId UniqueNetId); + + // Gets the persona name of a steam ID, STEAM ONLY, Returns empty if no result, might need RequestSteamFriendInfo called first. + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI") + static FString GetSteamPersonaName(const FBPUniqueNetId UniqueNetId); + + // Creates a unique steam id directly from a string holding a uint64 value, useful for testing + UFUNCTION(BlueprintPure, Category = "Online|AdvancedFriends|SteamAPI") + static FBPUniqueNetId CreateSteamIDFromString(const FString SteamID64); + + // Retreives the local steam ID from steam + UFUNCTION(BlueprintPure, Category = "Online|AdvancedFriends|SteamAPI") + static FBPUniqueNetId GetLocalSteamIDFromSteam(); + + /* Gets the current game played by a friend - AppID is int32 even though steam ids are uint32, can't be helped in blueprint currently + * can use the AppID with the WebAPI GetAppList request. + */ + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedFriends|SteamAPI", meta = (ExpandEnumAsExecs = "Result")) + static void GetSteamFriendGamePlayed(const FBPUniqueNetId UniqueNetId, EBlueprintResultSwitch &Result/*, FString & GameName*/, int32 & AppID); + + // Get a full list of steam groups + UFUNCTION(BlueprintCallable, Category = "Online|SteamAPI|SteamGroups") + static void GetSteamGroups(TArray & SteamGroups); + + // Initializes text filtering (pre-loading dictonaries) + // Returns if it succeeded, false if filtering is unavailable for the games language + UFUNCTION(BlueprintCallable, Category = "Online|SteamAPI|TextFiltering") + static bool InitTextFiltering(); + + // Attempts to filter a string with the given filtering context + // Returns true if the text has been filtered, false if it hasn't (no filtering required or operation failed) + // If false it will still output the original text + // Textsource is the steam id that is the source of the text (player name / chat) + // Requires that InitTextFiltering be called first!! + UFUNCTION(BlueprintCallable, Category = "Online|SteamAPI|TextFiltering") + static bool FilterText(FString TextToFilter, EBPTextFilteringContext Context, const FBPUniqueNetId TextSourceID, FString& FilteredText); + + // Returns if steam is running in big picture mode + UFUNCTION(BlueprintPure, Category = "Online|SteamAPI") + static bool IsSteamInBigPictureMode(); +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamSessions.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamSessions.h new file mode 100644 index 0000000..4a49251 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamSessions.h @@ -0,0 +1,12 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class AdvancedSteamSessions : public IModuleInterface +{ +public: + /** IModuleInterface implementation */ + void StartupModule(); + void ShutdownModule(); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamWorkshopLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamWorkshopLibrary.h new file mode 100644 index 0000000..64c5d76 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/AdvancedSteamWorkshopLibrary.h @@ -0,0 +1,351 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Online.h" +#include "OnlineSubsystem.h" +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +#include "steam/isteamugc.h" +#include "steam/isteamremotestorage.h" +#endif +#include "Interfaces/OnlineSessionInterface.h" + +// @todo Steam: Steam headers trigger secure-C-runtime warnings in Visual C++. Rather than mess with _CRT_SECURE_NO_WARNINGS, we'll just +// disable the warnings locally. Remove when this is fixed in the SDK +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +// #TODO check back on this at some point +#pragma warning(disable:4265) // SteamAPI CCallback< specifically, this warning is off by default but 4.17 turned it on.... +#endif + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + +#pragma push_macro("ARRAY_COUNT") +#undef ARRAY_COUNT + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(push)) +MSVC_PRAGMA(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) +#endif // USING_CODE_ANALYSIS + +#include + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(pop)) +#endif // USING_CODE_ANALYSIS + + +#pragma pop_macro("ARRAY_COUNT") + +#endif + +// @todo Steam: See above +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +#include "AdvancedSteamWorkshopLibrary.generated.h" + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(AdvancedSteamWorkshopLog, Log, All); + + +// Using a custom struct because uint32 isn't blueprint supported and I don't want to cast to int32 +// due to the size of the workshop it could end up overflowing? +USTRUCT(BlueprintType) +struct FBPSteamWorkshopID +{ + GENERATED_USTRUCT_BODY() + +public: + + uint64 SteamWorkshopID; + + FBPSteamWorkshopID() + { + + } + + FBPSteamWorkshopID(uint64 ID) + { + SteamWorkshopID = ID; + } +}; + + +// General result codes - Copying steams version over +// Check these to future proof +UENUM(BlueprintType) +enum class FBPSteamResult : uint8 +{ + K_EResultInvalid = 0, + k_EResultOK = 1, // success + k_EResultFail = 2, // generic failure + k_EResultNoConnection = 3, // no/failed network connection + // k_EResultNoConnectionRetry = 4, // OBSOLETE - removed + k_EResultInvalidPassword = 5, // password/ticket is invalid + k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere + k_EResultInvalidProtocolVer = 7, // protocol version is incorrect + k_EResultInvalidParam = 8, // a parameter is incorrect + k_EResultFileNotFound = 9, // file was not found + k_EResultBusy = 10, // called method busy - action not taken + k_EResultInvalidState = 11, // called object was in an invalid state + k_EResultInvalidName = 12, // name is invalid + k_EResultInvalidEmail = 13, // email is invalid + k_EResultDuplicateName = 14, // name is not unique + k_EResultAccessDenied = 15, // access is denied + k_EResultTimeout = 16, // operation timed out + k_EResultBanned = 17, // VAC2 banned + k_EResultAccountNotFound = 18, // account not found + k_EResultInvalidSteamID = 19, // steamID is invalid + k_EResultServiceUnavailable = 20, // The requested service is currently unavailable + k_EResultNotLoggedOn = 21, // The user is not logged on + k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party) + k_EResultEncryptionFailure = 23, // Encryption or Decryption failed + k_EResultInsufficientPrivilege = 24, // Insufficient privilege + k_EResultLimitExceeded = 25, // Too much of a good thing + k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes) + k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired + k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again + k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time + k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user + k_EResultIPNotFound = 31, // IP address not found + k_EResultPersistFailed = 32, // failed to write change to the data store + k_EResultLockingFailed = 33, // failed to acquire access lock for this operation + k_EResultLogonSessionReplaced = 34, + k_EResultConnectFailed = 35, + k_EResultHandshakeFailed = 36, + k_EResultIOFailure = 37, + k_EResultRemoteDisconnect = 38, + k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested + k_EResultBlocked = 40, // a user didn't allow it + k_EResultIgnored = 41, // target is ignoring sender + k_EResultNoMatch = 42, // nothing matching the request found + k_EResultAccountDisabled = 43, + k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. + k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed. + k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait + k_EResultSuspended = 51, // Long running operation (content download) suspended/paused + k_EResultCancelled = 52, // Operation canceled (typically by user: content download) + k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable + k_EResultDiskFull = 54, // Operation canceled - not enough disk space. + k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed + k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side + k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account + k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid + k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first + k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files + k_EResultIllegalPassword = 61, // The requested new password is not legal + k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer ) + k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure + k_EResultCannotUseOldPassword = 64, // The requested new password is not legal + k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid + k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent + k_EResultHardwareNotCapableOfIPT = 67, // + k_EResultIPTInitError = 68, // + k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user + k_EResultFacebookQueryError = 70, // Facebook query returned an error + k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired + k_EResultIPLoginRestrictionFailed = 72, + k_EResultAccountLockedDown = 73, + k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, + k_EResultNoMatchingURL = 75, + k_EResultBadResponse = 76, // parse failure, missing field, etc. + k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password + k_EResultValueOutOfRange = 78, // the value entered is outside the acceptable range + k_EResultUnexpectedError = 79, // something happened that we didn't expect to ever happen + k_EResultDisabled = 80, // The requested service has been configured to be unavailable + k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid ! + k_EResultRestrictedDevice = 82, // The device being used is not allowed to perform this action + k_EResultRegionLocked = 83, // The action could not be complete because it is region restricted + k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent + k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login + k_EResultItemDeleted = 86, // The thing we're trying to access has been deleted + k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker + k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch + k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match + k_EResultAccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners + k_EResultNotModified = 91, // data not modified +}; + +// Check these to future proof +UENUM(BlueprintType) +enum class FBPWorkshopFileType : uint8 +{ + k_EWorkshopFileTypeCommunity = 0, + k_EWorkshopFileTypeMicrotransaction = 1, + k_EWorkshopFileTypeCollection = 2, + k_EWorkshopFileTypeArt = 3, + k_EWorkshopFileTypeVideo = 4, + k_EWorkshopFileTypeScreenshot = 5, + k_EWorkshopFileTypeGame = 6, + k_EWorkshopFileTypeSoftware = 7, + k_EWorkshopFileTypeConcept = 8, + k_EWorkshopFileTypeWebGuide = 9, + k_EWorkshopFileTypeIntegratedGuide = 10, + k_EWorkshopFileTypeMerch = 11, + k_EWorkshopFileTypeControllerBinding = 12, + k_EWorkshopFileTypeSteamworksAccessInvite = 13, + k_EWorkshopFileTypeSteamVideo = 14, + + // Update k_EWorkshopFileTypeMax if you add values. + k_EWorkshopFileTypeMax = 15 +}; + +// WorkshopItemDetails Struct +USTRUCT(BlueprintType) +struct FBPSteamWorkshopItemDetails +{ + GENERATED_USTRUCT_BODY() + +public: + + FBPSteamWorkshopItemDetails() + { + ResultOfRequest = FBPSteamResult::k_EResultOK; + FileType = FBPWorkshopFileType::k_EWorkshopFileTypeMax; + CreatorAppID = 0; + ConsumerAppID = 0; + VotesUp = 0; + VotesDown = 0; + CalculatedScore = 0.f; + bBanned = false; + bAcceptedForUse = false; + bTagsTruncated = false; + } + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + FBPSteamWorkshopItemDetails(SteamUGCDetails_t &hUGCDetails) + { + ResultOfRequest = (FBPSteamResult)hUGCDetails.m_eResult; + FileType = (FBPWorkshopFileType)hUGCDetails.m_eFileType; + CreatorAppID = (int32)hUGCDetails.m_nCreatorAppID; + ConsumerAppID = (int32)hUGCDetails.m_nConsumerAppID; + Title = FString(hUGCDetails.m_rgchTitle, k_cchPublishedDocumentTitleMax); + Description = FString(hUGCDetails.m_rgchDescription, k_cchPublishedDocumentDescriptionMax); + ItemUrl = FString(hUGCDetails.m_rgchURL, k_cchPublishedFileURLMax); + VotesUp = (int32)hUGCDetails.m_unVotesUp; + VotesDown = (int32)hUGCDetails.m_unVotesDown; + CalculatedScore = hUGCDetails.m_flScore; + bBanned = hUGCDetails.m_bBanned; + bAcceptedForUse = hUGCDetails.m_bAcceptedForUse; + bTagsTruncated = hUGCDetails.m_bTagsTruncated; + + CreatorSteamID = FString::Printf(TEXT("%llu"), hUGCDetails.m_ulSteamIDOwner); + } + + FBPSteamWorkshopItemDetails(const SteamUGCDetails_t &hUGCDetails) + { + ResultOfRequest = (FBPSteamResult)hUGCDetails.m_eResult; + FileType = (FBPWorkshopFileType)hUGCDetails.m_eFileType; + CreatorAppID = (int32)hUGCDetails.m_nCreatorAppID; + ConsumerAppID = (int32)hUGCDetails.m_nConsumerAppID; + Title = FString(hUGCDetails.m_rgchTitle, k_cchPublishedDocumentTitleMax); + Description = FString(hUGCDetails.m_rgchDescription, k_cchPublishedDocumentDescriptionMax); + ItemUrl = FString(hUGCDetails.m_rgchURL, k_cchPublishedFileURLMax); + VotesUp = (int32)hUGCDetails.m_unVotesUp; + VotesDown = (int32)hUGCDetails.m_unVotesDown; + CalculatedScore = hUGCDetails.m_flScore; + bBanned = hUGCDetails.m_bBanned; + bAcceptedForUse = hUGCDetails.m_bAcceptedForUse; + bTagsTruncated = hUGCDetails.m_bTagsTruncated; + + CreatorSteamID = FString::Printf(TEXT("%llu"), hUGCDetails.m_ulSteamIDOwner); + } +#endif + + // Result of obtaining the details + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FBPSteamResult ResultOfRequest; + + // Type of file + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FBPWorkshopFileType FileType; + + // These two are listed as baked to an int, but is stored as a uint, think its safe to keep int + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + int32 CreatorAppID; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + int32 ConsumerAppID; + + // Title of item + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FString Title; + + // Description of item + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FString Description; + + //Url for a video of website + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FString ItemUrl; + + // Votes will be unlikely to go above signed limited + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + int32 VotesUp; + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + int32 VotesDown; + + // Calculated score + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + float CalculatedScore; + + // whether the file was banned + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + bool bBanned; + + // developer has specifically flagged this item as accepted in the Workshop + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + bool bAcceptedForUse; + + // whether the list of tags was too long to be returned in the provided buffer + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + bool bTagsTruncated; + + // Steam ID of the user who created this content. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|AdvancedSteamWorkshop") + FString CreatorSteamID; + + /* + PublishedFileId_t m_nPublishedFileId; + uint32 m_rtimeCreated; // time when the published file was created + uint32 m_rtimeUpdated; // time when the published file was last updated + uint32 m_rtimeAddedToUserList; // time when the user added the published file to their list (not always applicable) + ERemoteStoragePublishedFileVisibility m_eVisibility; // visibility + char m_rgchTags[k_cchTagListMax]; // comma separated list of all tags associated with this file + // file/url information + UGCHandle_t m_hFile; // The handle of the primary file + UGCHandle_t m_hPreviewFile; // The handle of the preview file + char m_pchFileName[k_cchFilenameMax]; // The cloud filename of the primary file + int32 m_nFileSize; // Size of the primary file + int32 m_nPreviewFileSize; // Size of the preview file + uint32 m_unNumChildren; // if m_eFileType == k_EWorkshopFileTypeCollection, then this number will be the number of children contained within the collection + */ + +}; + +UCLASS() +class UAdvancedSteamWorkshopLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + //********* Steam Functions *************// + + // Returns IDs for subscribed workshop items, TArray length dictates how many + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSteamWorkshop") + static TArray GetSubscribedWorkshopItems(int32 & NumberOfItems); + + UFUNCTION(BlueprintCallable, Category = "Online|AdvancedSteamWorkshop") + static void GetNumSubscribedWorkshopItems(int32 & NumberOfItems); + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamNotificationsSubsystem.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamNotificationsSubsystem.h new file mode 100644 index 0000000..a1b547c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamNotificationsSubsystem.h @@ -0,0 +1,90 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/GameInstanceSubsystem.h" + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + +//#include "OnlineSubsystemSteam.h" +//#include "OnlineSubsystemSteamPrivate.h" + +#include + +#endif + +#include "SteamNotificationsSubsystem.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSteamOverlayActivated, bool, bOverlayState); + +UCLASS() +class ADVANCEDSTEAMSESSIONS_API USteamNotificationsSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + + // Event thrown when the steam overlay switches states + UPROPERTY(BlueprintAssignable, Category = "SteamEvents") + FOnSteamOverlayActivated OnSteamOverlayActivated_Bind; + + USteamNotificationsSubsystem() : Super() + { + + } + + class cSteamEventsStore + { + public: + USteamNotificationsSubsystem* ParentSubsystem = nullptr; + void Initialize(USteamNotificationsSubsystem* MyParent) + { + ParentSubsystem = MyParent; + } + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + cSteamEventsStore() : + OnExternalUITriggeredCallback(this, &cSteamEventsStore::OnExternalUITriggered) + { + + } +#else + //cSteamEventsStore() + //{ + + //} +#endif + + //~cSteamEventsStore(){} + + private: +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + STEAM_CALLBACK(cSteamEventsStore, OnExternalUITriggered, GameOverlayActivated_t, OnExternalUITriggeredCallback); +#endif + }; + + cSteamEventsStore MyEvents; + + /** Implement this for initialization of instances of the system */ + virtual void Initialize(FSubsystemCollectionBase& Collection) override + { + MyEvents.Initialize(this); + } + + /** Implement this for deinitialization of instances of the system */ + virtual void Deinitialize() override + { + + } +}; + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +void USteamNotificationsSubsystem::cSteamEventsStore::OnExternalUITriggered(GameOverlayActivated_t* CallbackData) +{ + if (ParentSubsystem) + { + ParentSubsystem->OnSteamOverlayActivated_Bind.Broadcast((bool)CallbackData->m_bActive); + } +} +#endif \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamRequestGroupOfficersCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamRequestGroupOfficersCallbackProxy.h new file mode 100644 index 0000000..9c73a23 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamRequestGroupOfficersCallbackProxy.h @@ -0,0 +1,100 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "BlueprintDataDefinitions.h" + +// This is taken directly from UE4 - OnlineSubsystemSteamPrivatePCH.h as a fix for the array_count macro + +// @todo Steam: Steam headers trigger secure-C-runtime warnings in Visual C++. Rather than mess with _CRT_SECURE_NO_WARNINGS, we'll just +// disable the warnings locally. Remove when this is fixed in the SDK +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +// #TODO check back on this at some point +#pragma warning(disable:4265) // SteamAPI CCallback< specifically, this warning is off by default but 4.17 turned it on.... +#endif + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + +//#include "OnlineSubsystemSteam.h" + +#pragma push_macro("ARRAY_COUNT") +#undef ARRAY_COUNT + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(push)) +MSVC_PRAGMA(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) +#endif // USING_CODE_ANALYSIS + +#include + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(pop)) +#endif // USING_CODE_ANALYSIS + + +#pragma pop_macro("ARRAY_COUNT") + +#endif + +// @todo Steam: See above +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "SteamRequestGroupOfficersCallbackProxy.generated.h" + +USTRUCT(BlueprintType, Category = "Online|SteamAPI|SteamGroups") +struct FBPSteamGroupOfficer +{ + GENERATED_USTRUCT_BODY() + +public: + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + FBPUniqueNetId OfficerUniqueNetID; // Uint64 representation + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Online|SteamAPI|SteamGroups") + bool bIsOwner = false; + +}; + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBlueprintGroupOfficerDetailsDelegate, const TArray &, OfficerList); + +UCLASS(MinimalAPI) +class USteamRequestGroupOfficersCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + virtual ~USteamRequestGroupOfficersCallbackProxy(); + + // Called when there is a successful results return + UPROPERTY(BlueprintAssignable) + FBlueprintGroupOfficerDetailsDelegate OnSuccess; + + // Called when there is an unsuccessful results return + UPROPERTY(BlueprintAssignable) + FBlueprintGroupOfficerDetailsDelegate OnFailure; + + // Returns a list of steam group officers + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|SteamAPI|SteamGroups") + static USteamRequestGroupOfficersCallbackProxy* GetSteamGroupOfficerList(UObject* WorldContextObject, FBPUniqueNetId GroupUniqueNetID); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + void OnRequestGroupOfficerDetails( ClanOfficerListResponse_t *pResult, bool bIOFailure); + CCallResult m_callResultGroupOfficerRequestDetails; + +#endif + +private: + + FBPUniqueNetId GroupUniqueID; + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamWSRequestUGCDetailsCallbackProxy.h b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamWSRequestUGCDetailsCallbackProxy.h new file mode 100644 index 0000000..63d8c80 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Classes/SteamWSRequestUGCDetailsCallbackProxy.h @@ -0,0 +1,87 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "AdvancedSteamWorkshopLibrary.h" +#include "BlueprintDataDefinitions.h" + +// This is taken directly from UE4 - OnlineSubsystemSteamPrivatePCH.h as a fix for the array_count macro + +// @todo Steam: Steam headers trigger secure-C-runtime warnings in Visual C++. Rather than mess with _CRT_SECURE_NO_WARNINGS, we'll just +// disable the warnings locally. Remove when this is fixed in the SDK +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +// #TODO check back on this at some point +#pragma warning(disable:4265) // SteamAPI CCallback< specifically, this warning is off by default but 4.17 turned it on.... +#endif + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + +//#include "OnlineSubsystemSteam.h" + +#pragma push_macro("ARRAY_COUNT") +#undef ARRAY_COUNT + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(push)) +MSVC_PRAGMA(warning(disable : ALL_CODE_ANALYSIS_WARNINGS)) +#endif // USING_CODE_ANALYSIS + +#include + +#if USING_CODE_ANALYSIS +MSVC_PRAGMA(warning(pop)) +#endif // USING_CODE_ANALYSIS + + +#pragma pop_macro("ARRAY_COUNT") + +#endif + +// @todo Steam: See above +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +#include "SteamWSRequestUGCDetailsCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FBlueprintWorkshopDetailsDelegate, const FBPSteamWorkshopItemDetails&, WorkShopDetails); + +UCLASS(MinimalAPI) +class USteamWSRequestUGCDetailsCallbackProxy : public UOnlineBlueprintCallProxyBase +{ + GENERATED_UCLASS_BODY() + + // Called when there is a successful results return + UPROPERTY(BlueprintAssignable) + FBlueprintWorkshopDetailsDelegate OnSuccess; + + // Called when there is an unsuccessful results return + UPROPERTY(BlueprintAssignable) + FBlueprintWorkshopDetailsDelegate OnFailure; + + // Ends the current session + UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly = "true", WorldContext="WorldContextObject"), Category = "Online|AdvancedSteamWorkshop") + static USteamWSRequestUGCDetailsCallbackProxy* GetWorkshopItemDetails(UObject* WorldContextObject, FBPSteamWorkshopID WorkShopID); + + // UOnlineBlueprintCallProxyBase interface + virtual void Activate() override; + // End of UOnlineBlueprintCallProxyBase interface + +private: + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + // Internal callback when the operation completes, calls out to the public success/failure callbacks + + void OnUGCRequestUGCDetails(SteamUGCQueryCompleted_t *pResult, bool bIOFailure); + CCallResult m_callResultUGCRequestDetails; + +#endif + +private: + + FBPSteamWorkshopID WorkShopID; + UObject* WorldContextObject; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamFriendsLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamFriendsLibrary.cpp new file mode 100644 index 0000000..91f822e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamFriendsLibrary.cpp @@ -0,0 +1,432 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedSteamFriendsLibrary.h" +#include "OnlineSubSystemHeader.h" + +//General Log +DEFINE_LOG_CATEGORY(AdvancedSteamFriendsLog); + + +// Clan functions, add in soon +/*int32 UAdvancedSteamFriendsLibrary::GetFriendSteamLevel(const FBPUniqueNetId UniqueNetId) +{ + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("IsAFriend Had a bad UniqueNetId!")); + return 0; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + + + // clan (group) iteration and access functions + //virtual int GetClanCount() = 0; + //virtual CSteamID GetClanByIndex(int iClan) = 0; + //virtual const char *GetClanName(CSteamID steamIDClan) = 0; + //virtual const char *GetClanTag(CSteamID steamIDClan) = 0; + // returns the most recent information we have about what's happening in a clan + //virtual bool GetClanActivityCounts(CSteamID steamIDClan, int *pnOnline, int *pnInGame, int *pnChatting) = 0; + // for clans a user is a member of, they will have reasonably up-to-date information, but for others you'll have to download the info to have the latest + //virtual SteamAPICall_t DownloadClanActivityCounts(ARRAY_COUNT(cClansToRequest) CSteamID *psteamIDClans, int cClansToRequest) = 0; + + // requests information about a clan officer list + // when complete, data is returned in ClanOfficerListResponse_t call result + // this makes available the calls below + // you can only ask about clans that a user is a member of + // note that this won't download avatars automatically; if you get an officer, + // and no avatar image is available, call RequestUserInformation( steamID, false ) to download the avatar + //virtual SteamAPICall_t RequestClanOfficerList(CSteamID steamIDClan) = 0; + + + // returns the steamID of the clan owner + //virtual CSteamID GetClanOwner(CSteamID steamIDClan) = 0; + // returns the number of officers in a clan (including the owner) + //virtual int GetClanOfficerCount(CSteamID steamIDClan) = 0; + // returns the steamID of a clan officer, by index, of range [0,GetClanOfficerCount) + //virtual CSteamID GetClanOfficerByIndex(CSteamID steamIDClan, int iOfficer) = 0; + + + return SteamFriends()->GetFriendSteamLevel(id); + } +#endif + + return 0; +}*/ + +void UAdvancedSteamFriendsLibrary::GetSteamGroups(TArray & SteamGroups) +{ + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + int numClans = SteamFriends()->GetClanCount(); + + for (int i = 0; i < numClans; i++) + { + CSteamID SteamGroupID = SteamFriends()->GetClanByIndex(i); + + if(!SteamGroupID.IsValid()) + continue; + + FBPSteamGroupInfo GroupInfo; + + TSharedPtr ValueID(new const FUniqueNetIdSteam2(SteamGroupID)); + GroupInfo.GroupID.SetUniqueNetId(ValueID); + SteamFriends()->GetClanActivityCounts(SteamGroupID, &GroupInfo.numOnline, &GroupInfo.numInGame, &GroupInfo.numChatting); + GroupInfo.GroupName = FString(UTF8_TO_TCHAR(SteamFriends()->GetClanName(SteamGroupID))); + GroupInfo.GroupTag = FString(UTF8_TO_TCHAR(SteamFriends()->GetClanTag(SteamGroupID))); + + SteamGroups.Add(GroupInfo); + } + } +#endif + +} + +void UAdvancedSteamFriendsLibrary::GetSteamFriendGamePlayed(const FBPUniqueNetId UniqueNetId, EBlueprintResultSwitch &Result/*, FString & GameName*/, int32 & AppID) +{ + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("GetSteamFriendGamePlayed Had a bad UniqueNetId!")); + Result = EBlueprintResultSwitch::OnFailure; + return; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + + FriendGameInfo_t GameInfo; + bool bIsInGame = SteamFriends()->GetFriendGamePlayed(id, &GameInfo); + + if (bIsInGame && GameInfo.m_gameID.IsValid()) + { + AppID = GameInfo.m_gameID.AppID(); + + // Forgot this test and left it in, it is incorrect, you would need restricted access + // And it would only find games in the local library anyway + /*char NameBuffer[512]; + int Len = SteamAppList()->GetAppName(GameInfo.m_gameID.AppID(), NameBuffer, 512); + + if (Len != -1) // Invalid + { + GameName = FString(UTF8_TO_TCHAR(NameBuffer)); + }*/ + + Result = EBlueprintResultSwitch::OnSuccess; + return; + } + + } +#endif + + Result = EBlueprintResultSwitch::OnFailure; +} + +int32 UAdvancedSteamFriendsLibrary::GetFriendSteamLevel(const FBPUniqueNetId UniqueNetId) +{ + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("IsAFriend Had a bad UniqueNetId!")); + return 0; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + + return SteamFriends()->GetFriendSteamLevel(id); + } +#endif + + return 0; +} + +FString UAdvancedSteamFriendsLibrary::GetSteamPersonaName(const FBPUniqueNetId UniqueNetId) +{ + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("GetSteamPersonaName Had a bad UniqueNetId!")); + return FString(TEXT("")); + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + const char* PersonaName = SteamFriends()->GetFriendPersonaName(id); + return FString(UTF8_TO_TCHAR(PersonaName)); + } +#endif + + return FString(TEXT("")); +} + +FBPUniqueNetId UAdvancedSteamFriendsLibrary::CreateSteamIDFromString(const FString SteamID64) +{ + FBPUniqueNetId netId; + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!(SteamID64.Len() > 0)) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("CreateSteamIDFromString Had a bad UniqueNetId!")); + return netId; + } + + if (SteamAPI_Init()) + { + // Already does the conversion + TSharedPtr ValueID(new const FUniqueNetIdSteam2(SteamID64)); + //FCString::Atoi64(*SteamID64)); + + netId.SetUniqueNetId(ValueID); + return netId; + } +#endif + + return netId; +} + +FBPUniqueNetId UAdvancedSteamFriendsLibrary::GetLocalSteamIDFromSteam() +{ + FBPUniqueNetId netId; + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (SteamAPI_Init()) + { + TSharedPtr SteamID(new const FUniqueNetIdSteam2(SteamUser()->GetSteamID())); + netId.SetUniqueNetId(SteamID); + } +#endif + + return netId; +} + +bool UAdvancedSteamFriendsLibrary::RequestSteamFriendInfo(const FBPUniqueNetId UniqueNetId, bool bRequireNameOnly) +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("RequestSteamFriendInfo Had a bad UniqueNetId!")); + return false; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + + return !SteamFriends()->RequestUserInformation(id, bRequireNameOnly); + } +#endif + + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("RequestSteamFriendInfo Couldn't init steamAPI!")); + return false; +} + + +bool UAdvancedSteamFriendsLibrary::OpenSteamUserOverlay(const FBPUniqueNetId UniqueNetId, ESteamUserOverlayType DialogType) +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("OpenSteamUserOverlay Had a bad UniqueNetId!")); + return false; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + FString DialogName = EnumToString("ESteamUserOverlayType", (uint8)DialogType); + SteamFriends()->ActivateGameOverlayToUser(TCHAR_TO_ANSI(*DialogName), id); + return true; + } +#endif + + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("OpenSteamUserOverlay Couldn't init steamAPI!")); + return false; +} + +bool UAdvancedSteamFriendsLibrary::IsOverlayEnabled() +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (SteamAPI_Init()) + { + return SteamUtils()->IsOverlayEnabled(); + } +#endif + + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("OpenSteamUserOverlay Couldn't init steamAPI!")); + return false; +} + +UTexture2D * UAdvancedSteamFriendsLibrary::GetSteamFriendAvatar(const FBPUniqueNetId UniqueNetId, EBlueprintAsyncResultSwitch &Result, SteamAvatarSize AvatarSize) +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (!UniqueNetId.IsValid() || !UniqueNetId.UniqueNetId->IsValid() || UniqueNetId.UniqueNetId->GetType() != STEAM_SUBSYSTEM) + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("GetSteamFriendAvatar Had a bad UniqueNetId!")); + Result = EBlueprintAsyncResultSwitch::OnFailure; + return nullptr; + } + + uint32 Width = 0; + uint32 Height = 0; + + if (SteamAPI_Init()) + { + //Getting the PictureID from the SteamAPI and getting the Size with the ID + //virtual bool RequestUserInformation( CSteamID steamIDUser, bool bRequireNameOnly ) = 0; + + + uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes()); + int Picture = 0; + + switch(AvatarSize) + { + case SteamAvatarSize::SteamAvatar_Small: Picture = SteamFriends()->GetSmallFriendAvatar(id); break; + case SteamAvatarSize::SteamAvatar_Medium: Picture = SteamFriends()->GetMediumFriendAvatar(id); break; + case SteamAvatarSize::SteamAvatar_Large: Picture = SteamFriends()->GetLargeFriendAvatar(id); break; + default: break; + } + + if (Picture == -1) + { + Result = EBlueprintAsyncResultSwitch::AsyncLoading; + return NULL; + } + + SteamUtils()->GetImageSize(Picture, &Width, &Height); + + // STOLEN FROM ANSWERHUB :p, then fixed because answerhub wasn't releasing the memory O.o + // Also fixed image pixel format and switched to a memcpy instead of manual iteration. + // At some point I should probably reply to that answerhub post with these fixes to prevent people killing their games..... + + if (Width > 0 && Height > 0) + { + //Creating the buffer "oAvatarRGBA" and then filling it with the RGBA Stream from the Steam Avatar + uint8 *oAvatarRGBA = new uint8[Width * Height * 4]; + + + //Filling the buffer with the RGBA Stream from the Steam Avatar and creating a UTextur2D to parse the RGBA Steam in + SteamUtils()->GetImageRGBA(Picture, (uint8*)oAvatarRGBA, 4 * Height * Width * sizeof(char)); + + + // Removed as I changed the image bit code to be RGB, I think the original author was unaware that there were different pixel formats + /* + //Swap R and B channels because for some reason the games whack + for (uint32 i = 0; i < (Width * Height * 4); i += 4) + { + uint8 Temp = oAvatarRGBA[i + 0]; + oAvatarRGBA[i + 0] = oAvatarRGBA[i + 2]; + oAvatarRGBA[i + 2] = Temp; + }*/ + + UTexture2D* Avatar = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8); + // Switched to a Memcpy instead of byte by byte transer + + if (FTexturePlatformData* PlatformData = Avatar->GetPlatformData()) + { + uint8* MipData = (uint8*)PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(MipData, (void*)oAvatarRGBA, Height * Width * 4); + PlatformData->Mips[0].BulkData.Unlock(); + + // Original implementation was missing this!! + // the hell man...... + delete[] oAvatarRGBA; + + //Setting some Parameters for the Texture and finally returning it + PlatformData->SetNumSlices(1); + Avatar->NeverStream = true; + //Avatar->CompressionSettings = TC_EditorIcon; + } + + Avatar->UpdateResource(); + + Result = EBlueprintAsyncResultSwitch::OnSuccess; + return Avatar; + } + else + { + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("Bad Height / Width with steam avatar!")); + } + + Result = EBlueprintAsyncResultSwitch::OnFailure; + return nullptr; + } +#endif + + UE_LOG(AdvancedSteamFriendsLog, Warning, TEXT("STEAM Couldn't be verified as initialized")); + Result = EBlueprintAsyncResultSwitch::OnFailure; + return nullptr; +} + +bool UAdvancedSteamFriendsLibrary::InitTextFiltering() +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + return SteamUtils()->InitFilterText(); + } + +#endif + + return false; +} + +bool UAdvancedSteamFriendsLibrary::FilterText(FString TextToFilter, EBPTextFilteringContext Context, const FBPUniqueNetId TextSourceID, FString& FilteredText) +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + uint32 BufferLen = TextToFilter.Len() + 10; // Docs say 1 byte excess min, going with 10 + char* OutText = new char[BufferLen]; + + uint64 id = 0; + + if (TextSourceID.IsValid()) + { + id = *((uint64*)TextSourceID.UniqueNetId->GetBytes()); + } + + int FilterCount = SteamUtils()->FilterText((ETextFilteringContext)Context, id, TCHAR_TO_ANSI(*TextToFilter), OutText, BufferLen); + + if (FilterCount > 0) + { + FilteredText = FString(UTF8_TO_TCHAR(OutText)); + delete[] OutText; + return true; + } + + delete[] OutText; + } + +#endif + + FilteredText = TextToFilter; + return false; +} + +bool UAdvancedSteamFriendsLibrary::IsSteamInBigPictureMode() +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + return SteamUtils()->IsSteamInBigPictureMode(); + } + +#endif + + return false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamSessions.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamSessions.cpp new file mode 100644 index 0000000..34d9ff5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamSessions.cpp @@ -0,0 +1,12 @@ +//#include "StandAlonePrivatePCH.h" +#include "AdvancedSteamSessions.h" + +void AdvancedSteamSessions::StartupModule() +{ +} + +void AdvancedSteamSessions::ShutdownModule() +{ +} + +IMPLEMENT_MODULE(AdvancedSteamSessions, AdvancedSteamSessions) \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamWorkshopLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamWorkshopLibrary.cpp new file mode 100644 index 0000000..497f2b0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/AdvancedSteamWorkshopLibrary.cpp @@ -0,0 +1,69 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AdvancedSteamWorkshopLibrary.h" +#include "OnlineSubSystemHeader.h" +//General Log +DEFINE_LOG_CATEGORY(AdvancedSteamWorkshopLog); + + +void UAdvancedSteamWorkshopLibrary::GetNumSubscribedWorkshopItems(int32 & NumberOfItems) +{ + NumberOfItems = 0; +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + NumberOfItems = SteamUGC()->GetNumSubscribedItems(); + return; + } + else + { + UE_LOG(AdvancedSteamWorkshopLog, Warning, TEXT("Error in GetNumSubscribedWorkshopItemCount : SteamAPI is not Inited!")); + return; + } +#endif + + UE_LOG(AdvancedSteamWorkshopLog, Warning, TEXT("Error in GetNumSubscribedWorkshopItemCount : Called on an incompatible platform")); + return; +} + +TArray UAdvancedSteamWorkshopLibrary::GetSubscribedWorkshopItems(int32 & NumberOfItems) +{ + TArray outArray; + NumberOfItems = 0; + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + + if (SteamAPI_Init()) + { + uint32 NumItems = SteamUGC()->GetNumSubscribedItems(); + + if (NumItems == 0) + return outArray; + + // Not using the actual variable above in case someone somehow goes past int32 limits + // Don't want to go negative on the iteration. + NumberOfItems = NumItems; + + PublishedFileId_t *fileIds = new PublishedFileId_t[NumItems]; + + uint32 subItems = SteamUGC()->GetSubscribedItems(fileIds, NumItems); + + for (uint32 i = 0; i < subItems; ++i) + { + outArray.Add(FBPSteamWorkshopID(fileIds[i])); + } + + delete[] fileIds; + + return outArray; + } + else + { + UE_LOG(AdvancedSteamWorkshopLog, Warning, TEXT("Error in GetSubscribedWorkshopItemCount : SteamAPI is not Inited!")); + return outArray; + } +#endif + + UE_LOG(AdvancedSteamWorkshopLog, Warning, TEXT("Error in GetSubscribedWorkshopItemCount : Called on an incompatible platform")); + return outArray; +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamRequestGroupOfficersCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamRequestGroupOfficersCallbackProxy.cpp new file mode 100644 index 0000000..337239c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamRequestGroupOfficersCallbackProxy.cpp @@ -0,0 +1,121 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "SteamRequestGroupOfficersCallbackProxy.h" +#include "Online/CoreOnline.h" +#include "AdvancedSteamFriendsLibrary.h" +#include "OnlineSubSystemHeader.h" +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +#include "steam/isteamfriends.h" +#endif +//#include "OnlineSubsystemSteamTypes.h" + +////////////////////////////////////////////////////////////////////////// +// UEndSessionCallbackProxy + +USteamRequestGroupOfficersCallbackProxy::USteamRequestGroupOfficersCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +USteamRequestGroupOfficersCallbackProxy::~USteamRequestGroupOfficersCallbackProxy() +{ +} + +USteamRequestGroupOfficersCallbackProxy* USteamRequestGroupOfficersCallbackProxy::GetSteamGroupOfficerList(UObject* WorldContextObject, FBPUniqueNetId GroupUniqueNetID) +{ + USteamRequestGroupOfficersCallbackProxy* Proxy = NewObject(); + + Proxy->GroupUniqueID = GroupUniqueNetID; + return Proxy; +} + +void USteamRequestGroupOfficersCallbackProxy::Activate() +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)GroupUniqueID.UniqueNetId->GetBytes()); + SteamAPICall_t hSteamAPICall = SteamFriends()->RequestClanOfficerList(id); + + m_callResultGroupOfficerRequestDetails.Set(hSteamAPICall, this, &USteamRequestGroupOfficersCallbackProxy::OnRequestGroupOfficerDetails); + return; + } +#endif + TArray EmptyArray; + OnFailure.Broadcast(EmptyArray); +} + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +void USteamRequestGroupOfficersCallbackProxy::OnRequestGroupOfficerDetails(ClanOfficerListResponse_t *pResult, bool bIOFailure) +{ + TArray OfficerArray; + + //FOnlineSubsystemSteam* SteamSubsystem = (FOnlineSubsystemSteam*)(IOnlineSubsystem::Get(STEAM_SUBSYSTEM)); + + if (bIOFailure || !pResult || !pResult->m_bSuccess) + { + //if (SteamSubsystem != nullptr) + { + // SteamSubsystem->ExecuteNextTick([this]() + //{ + TArray FailureArray; + OnFailure.Broadcast(FailureArray); + //}); + } + //OnFailure.Broadcast(OfficerArray); + return; + } + + if (SteamAPI_Init()) + { + uint64 id = *((uint64*)GroupUniqueID.UniqueNetId->GetBytes()); + + FBPSteamGroupOfficer Officer; + CSteamID ClanOwner = SteamFriends()->GetClanOwner(id); + + Officer.bIsOwner = true; + + TSharedPtr ValueID(new const FUniqueNetIdSteam2(ClanOwner)); + Officer.OfficerUniqueNetID.SetUniqueNetId(ValueID); + OfficerArray.Add(Officer); + + for (int i = 0; i < pResult->m_cOfficers; i++) + { + CSteamID OfficerSteamID = SteamFriends()->GetClanOfficerByIndex(id, i); + + Officer.bIsOwner = false; + + TSharedPtr newValueID(new const FUniqueNetIdSteam2(OfficerSteamID)); + Officer.OfficerUniqueNetID.SetUniqueNetId(newValueID); + + OfficerArray.Add(Officer); + } + + //if (SteamSubsystem != nullptr) + //{ + //SteamSubsystem->ExecuteNextTick([OfficerArray, this]() + //{ + OnSuccess.Broadcast(OfficerArray); + //}); + //} + + //OnSuccess.Broadcast(OfficerArray); + return; + } + else + { + //if (SteamSubsystem != nullptr) + { + //SteamSubsystem->ExecuteNextTick([this]() + //{ + TArray FailureArray; + OnFailure.Broadcast(FailureArray); + //}); + } + } + + // Should never hit this anyway + //OnFailure.Broadcast(OfficerArray); +} +#endif + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamWSRequestUGCDetailsCallbackProxy.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamWSRequestUGCDetailsCallbackProxy.cpp new file mode 100644 index 0000000..95bee5c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/AdvancedSteamSessions/Source/AdvancedSteamSessions/Private/SteamWSRequestUGCDetailsCallbackProxy.cpp @@ -0,0 +1,101 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "SteamWSRequestUGCDetailsCallbackProxy.h" +#include "OnlineSubSystemHeader.h" +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +#include "steam/isteamugc.h" +#endif + +////////////////////////////////////////////////////////////////////////// +// UEndSessionCallbackProxy + +USteamWSRequestUGCDetailsCallbackProxy::USteamWSRequestUGCDetailsCallbackProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +USteamWSRequestUGCDetailsCallbackProxy* USteamWSRequestUGCDetailsCallbackProxy::GetWorkshopItemDetails(UObject* WorldContextObject, FBPSteamWorkshopID WorkShopID/*, int32 NumSecondsBeforeTimeout*/) +{ + USteamWSRequestUGCDetailsCallbackProxy* Proxy = NewObject(); + + Proxy->WorkShopID = WorkShopID; + return Proxy; +} + +void USteamWSRequestUGCDetailsCallbackProxy::Activate() +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + if (SteamAPI_Init()) + { + // #TODO: Support arrays instead in the future? + UGCQueryHandle_t hQueryHandle = SteamUGC()->CreateQueryUGCDetailsRequest((PublishedFileId_t *)&WorkShopID.SteamWorkshopID, 1); + // #TODO: add search settings here by calling into the handle? + SteamAPICall_t hSteamAPICall = SteamUGC()->SendQueryUGCRequest(hQueryHandle); + + // Need to release the query + SteamUGC()->ReleaseQueryUGCRequest(hQueryHandle); + + if (hSteamAPICall == k_uAPICallInvalid) + { + OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); + return; + } + + m_callResultUGCRequestDetails.Set(hSteamAPICall, this, &USteamWSRequestUGCDetailsCallbackProxy::OnUGCRequestUGCDetails); + return; + } +#endif + OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); +} + +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +void USteamWSRequestUGCDetailsCallbackProxy::OnUGCRequestUGCDetails(SteamUGCQueryCompleted_t *pResult, bool bIOFailure) +{ + //FOnlineSubsystemSteam* SteamSubsystem = (FOnlineSubsystemSteam*)(IOnlineSubsystem::Get(STEAM_SUBSYSTEM)); + + if (bIOFailure || !pResult || pResult->m_unNumResultsReturned <= 0) + { + //if (SteamSubsystem != nullptr) + { + // SteamSubsystem->ExecuteNextTick([this]() + //{ + OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); + //}); + } + //OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); + return; + } + if (SteamAPI_Init()) + { + SteamUGCDetails_t Details; + if (SteamUGC()->GetQueryUGCResult(pResult->m_handle, 0, &Details)) + { + //if (SteamSubsystem != nullptr) + { + //SteamSubsystem->ExecuteNextTick([Details, this]() + //{ + OnSuccess.Broadcast(FBPSteamWorkshopItemDetails(Details)); + //}); + } + + //OnSuccess.Broadcast(FBPSteamWorkshopItemDetails(Details)); + return; + } + } + else + { + //if (SteamSubsystem != nullptr) + { + //SteamSubsystem->ExecuteNextTick([this]() + //{ + OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); + //}); + } + } + + // Not needed, should never hit here + //OnFailure.Broadcast(FBPSteamWorkshopItemDetails()); +} +#endif + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/LICENSE.txt b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/LICENSE.txt new file mode 100644 index 0000000..986c999 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright Joshua Statzer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/README.md b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/README.md new file mode 100644 index 0000000..c83724b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/AdvancedSessions/README.md @@ -0,0 +1,7 @@ +### How do I use it? ### + +**KantanDocGen Automatic Documentation ([KantanDocGen](http://kantandev.com/free/kantan-doc-gen))** + +**[AdvancedSessions](https://vreue4.com/generated-node-documentation?section=advanced-sessions-plugin)** + +**[AdvancedSteamSessions](https://vreue4.com/generated-node-documentation?section=advanced-steam-sessions-plugin)** diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitattributes b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitattributes new file mode 100644 index 0000000..3373152 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.bat eol=crlf \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitignore b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitignore new file mode 100644 index 0000000..3cdb673 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/.gitignore @@ -0,0 +1,10 @@ + +.hg/ +binaries/ +deriveddatacache/ +.vs/ +build/ +intermediate/ +PACKPLUGIN/ +saved/ +*.orig \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/LICENSE.txt b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/LICENSE.txt new file mode 100644 index 0000000..986c999 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright Joshua Statzer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[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 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin new file mode 100644 index 0000000..ef2e2b0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin @@ -0,0 +1,45 @@ +{ + "FileVersion": 3, + "Version": 5.3, + "VersionName": "5.3", + "FriendlyName": "OpenXRExpansionPlugin", + "Description": "An set of utility functions for OpenXR", + "Category": "Virtual Reality", + "CreatedBy": "Joshua (MordenTral) Statzer", + "CreatedByURL": "http://www.vreue4.com", + "DocsURL": "http://www.vreue4.com", + "MarketplaceURL": "", + "SupportURL": "http://www.vreue4.com", + "EnabledByDefault": false, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ], + "Modules": [ + { + "Name": "OpenXRExpansionPlugin", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + } + , + { + "Name": "OpenXRExpansionEditor", + "Type": "UnCookedOnly", + "LoadingPhase": "PostEngineInit" + } + ], + "Plugins": [ + { + "Name": "OpenXR", + "Enabled": true + }, + { + "Name": "XRBase", + "Enabled": true + } + ] +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png new file mode 100644 index 0000000..23fec31 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8f329acc9d0c2815d28349d5a4144ebd337f7e2a93819cf1a7c3cc9b06ecea +size 16085 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs new file mode 100644 index 0000000..36288b9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs @@ -0,0 +1,61 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OpenXRExpansionEditor : ModuleRules + { + + public OpenXRExpansionEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + // ... add other public dependencies that you statically link with here ... + "Engine", + "Core", + "CoreUObject" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "BlueprintGraph", + "AnimGraph", + "AnimGraphRuntime", + "SlateCore", + "Slate", + "InputCore", + "Engine", + "EditorStyle", + "AssetRegistry", + "OpenXRExpansionPlugin" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp new file mode 100644 index 0000000..1a109c9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp @@ -0,0 +1,34 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AnimGraphNode_ApplyOpenXRHandPose.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimGraphNode_ApplyOpenXRHandPose) + +///////////////////////////////////////////////////// +// UAnimGraphNode_ModifyBSHand + +UAnimGraphNode_ApplyOpenXRHandPose::UAnimGraphNode_ApplyOpenXRHandPose(const FObjectInitializer& Initializer) + : Super(Initializer) +{ +} + +//Title Color! +FLinearColor UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitleColor() const +{ + return FLinearColor(12, 12, 0, 1); +} + +//Node Category +FString UAnimGraphNode_ApplyOpenXRHandPose::GetNodeCategory() const +{ + return FString("OpenXR"); +} +FText UAnimGraphNode_ApplyOpenXRHandPose::GetControllerDescription() const +{ + return FText::FromString("Apply OpenXR Hand Pose"); +} + +FText UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + FText Result = GetControllerDescription(); + return Result; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp new file mode 100644 index 0000000..f22c5fc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp @@ -0,0 +1,16 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "OpenXRExpansionEditor.h" +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" + + +IMPLEMENT_MODULE(FOpenXRExpansionEditorModule, OpenXRExpansionEditor); + +void FOpenXRExpansionEditorModule::StartupModule() +{ +} + +void FOpenXRExpansionEditorModule::ShutdownModule() +{ +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h new file mode 100644 index 0000000..b9d5db3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h @@ -0,0 +1,34 @@ +#pragma once + +#include "AnimGraphDefinitions.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Editor/AnimGraph/Public/AnimGraphNode_SkeletalControlBase.h" + +#include "AnimNode_ApplyOpenXRHandPose.h" +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AnimGraphNode_ApplyOpenXRHandPose.generated.h" + +UCLASS(MinimalAPI) +class UAnimGraphNode_ApplyOpenXRHandPose : public UAnimGraphNode_SkeletalControlBase +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category = Settings) + FAnimNode_ApplyOpenXRHandPose Node; + +public: + // UEdGraphNode interface + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual FLinearColor GetNodeTitleColor() const override; + virtual FString GetNodeCategory() const override; + // End of UEdGraphNode interface + +protected: + + // UAnimGraphNode_SkeletalControlBase protected interface + virtual FText GetControllerDescription() const; + virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; } + // End of UAnimGraphNode_SkeletalControlBase protected interface + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h new file mode 100644 index 0000000..92ed6bc --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h @@ -0,0 +1,13 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Runtime/Core/Public/Modules/ModuleInterface.h" + +class FOpenXRExpansionEditorModule : public IModuleInterface +{ +public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs new file mode 100644 index 0000000..5e4b5d1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs @@ -0,0 +1,70 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OpenXRExpansionPlugin: ModuleRules + { + public OpenXRExpansionPlugin(ReadOnlyTargetRules Target) + : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + //"InputDevice", + //"LiveLink", + //"LiveLinkInterface" + } + ); + + var EngineDir = Path.GetFullPath(Target.RelativeEnginePath); + PrivateIncludePaths.AddRange( + new string[] { + EngineDir + "Plugins/Runtime/OpenXR/Source/OpenXRHMD/Private", + EngineDir + "/Source/ThirdParty/OpenXR/include", + // ... add other private include paths required here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "NetCore", + "CoreUObject", + //"ApplicationCore", + "Engine", + //"InputDevice", + "InputCore", + "Slate", + "HeadMountedDisplay", + //"AnimGraph", + "AnimGraphRuntime", + "SlateCore", + "XRBase" + //"LiveLink", + //"LiveLinkInterface", + } + ); + + if (Target.Platform != UnrealTargetPlatform.Mac) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "OpenXRHMD" + } + ); + PrivateDefinitions.AddRange(new string[] { "OPENXR_SUPPORTED" }); + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenXR"); + } + + // if (Target.bBuildEditor == true) + // { + // PrivateDependencyModuleNames.Add("UnrealEd"); + // } + } + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp new file mode 100644 index 0000000..6a14619 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp @@ -0,0 +1,463 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AnimNode_ApplyOpenXRHandPose.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_ApplyOpenXRHandPose) + +//#include "EngineMinimal.h" +//#include "Engine/Engine.h" +//#include "CoreMinimal.h" +#include "OpenXRExpansionFunctionLibrary.h" +#include "AnimNode_ApplyOpenXRHandPose.h" +#include "AnimationRuntime.h" +#include "DrawDebugHelpers.h" +#include "OpenXRHandPoseComponent.h" +#include "Runtime/Engine/Public/Animation/AnimInstanceProxy.h" +#include "BoneControllers/AnimNode_SkeletalControlBase.h" + +FAnimNode_ApplyOpenXRHandPose::FAnimNode_ApplyOpenXRHandPose() + : FAnimNode_SkeletalControlBase() +{ + WorldIsGame = false; + Alpha = 1.f; + SkeletonType = EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right; + bIsOpenInputAnimationInstance = false; + bSkipRootBone = false; + bOnlyApplyWristTransform = false; + //WristAdjustment = FQuat::Identity; +} + +void FAnimNode_ApplyOpenXRHandPose::OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance) +{ + Super::OnInitializeAnimInstance(InProxy, InAnimInstance); + + if (const UOpenXRAnimInstance * animInst = Cast(InAnimInstance)) + { + bIsOpenInputAnimationInstance = true; + } +} + +void FAnimNode_ApplyOpenXRHandPose::Initialize_AnyThread(const FAnimationInitializeContext& Context) +{ + Super::Initialize_AnyThread(Context); +} + +void FAnimNode_ApplyOpenXRHandPose::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) +{ + Super::CacheBones_AnyThread(Context); +} + +void FAnimNode_ApplyOpenXRHandPose::InitializeBoneReferences(const FBoneContainer& RequiredBones) +{ + UObject* OwningAsset = RequiredBones.GetAsset(); + if (!OwningAsset) + return; + + USkeleton* AssetSkeleton = RequiredBones.GetSkeletonAsset(); + + if (!AssetSkeleton) + return; + + if (!MappedBonePairs.bInitialized || OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton) + { + + // Trigger a full re-build if our asset changed + if (MappedBonePairs.bInitialized && (OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton)) + { + MappedBonePairs.ClearMapping(); + } + + MappedBonePairs.LastInitializedName = OwningAsset->GetFName(); + MappedBonePairs.LastInitializedSkeleton = SkeletonType; + MappedBonePairs.bInitialized = false; + + if (AssetSkeleton) + { + // If our bone pairs are empty, then setup our sane defaults + if (!MappedBonePairs.BonePairs.Num()) + { + + MappedBonePairs.ConstructDefaultMappings(SkeletonType, bSkipRootBone); + } + + // Construct a reverse map of our joints + MappedBonePairs.ConstructReverseMapping(); + + TArray RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose(); + TArray RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo(); + + for (FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs) + { + // Fill in the bone name for the reference + BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget; + + // Init the reference + BonePair.ReferenceToConstruct.Initialize(AssetSkeleton); + + BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones); + + if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE)) + { + // Get our parent bones index + BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + } + } + + MappedBonePairs.bInitialized = true; + + if (SkeletonType == EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left || SkeletonType == EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Right) + { + // We hard code this for now because I don't like their wrist being a different transform + MappedBonePairs.AdjustmentQuat = FRotator(0.f, 90.f, 180.f).Quaternion(); // Current one is incorrect without wrist + // Maybe do it in relative space? + } + else + { + CalculateSkeletalAdjustment(AssetSkeleton); + } + + } + } +} + +void FAnimNode_ApplyOpenXRHandPose::CalculateSkeletalAdjustment(USkeleton* AssetSkeleton) +{ + + TArray RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose(); + TArray RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo(); + + if (!MappedBonePairs.bInitialized || MappedBonePairs.BonePairs.Num() < 4 || !RefBones.Num()) + { + UE_LOG(LogTemp, Error, TEXT("Empty or incorrect mapping or skeleton data when calculating skeletal adjustment!")); + return; + } + + FBPOpenXRSkeletalPair KnuckleIndexPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT]]; + FBPOpenXRSkeletalPair KnuckleMiddlePair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT]]; + FBPOpenXRSkeletalPair KnuckleRingPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT]]; + FBPOpenXRSkeletalPair KnucklePinkyPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT]]; + + FBPOpenXRSkeletalPair WristPair = MappedBonePairs.BonePairs[MappedBonePairs.ReverseBonePairMap[(int8)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT]]; + + FVector KnuckleAverage = GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleIndexPair.ReferenceToConstruct.BoneIndex).GetTranslation(); + KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleMiddlePair.ReferenceToConstruct.BoneIndex).GetTranslation(); + KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnuckleRingPair.ReferenceToConstruct.BoneIndex).GetTranslation(); + KnuckleAverage += GetRefBoneInCS(RefBones, RefBonesInfo, KnucklePinkyPair.ReferenceToConstruct.BoneIndex).GetTranslation(); + + // Get our average across the knuckles + KnuckleAverage /= 4.f; + + // Obtain the UE4 wrist Side & Forward directions from first animation frame and place in cache + FTransform WristTransform_UE = GetRefBoneInCS(RefBones, RefBonesInfo, WristPair.ReferenceToConstruct.BoneIndex); + FVector ToKnuckleAverage_UE = KnuckleAverage - WristTransform_UE.GetTranslation(); + ToKnuckleAverage_UE.Normalize(); + + + WristForwardLS_UE = WristTransform_UE.GetRotation().UnrotateVector(ToKnuckleAverage_UE); + SetVectorToMaxElement(WristForwardLS_UE); + WristSideDirectionLS = FVector::CrossProduct(WristForwardLS_UE, FVector::RightVector); + SetVectorToMaxElement(WristSideDirectionLS); + + CalculateOpenXRAdjustment(); +} + +void FAnimNode_ApplyOpenXRHandPose::CalculateOpenXRAdjustment() +{ + // Base implementation is like valves + + // Forward direction + static FVector OpenXRForwardDirection = FVector(1.0f, 0.f, 0.f); + + // Side direction + // Do I need to flip this for left hand? + static FVector OpenXRSideDirection = FVector(0.f, 1.f, 0.f); + + // Align forward vectors, openXR once in engine is X+ forward + FQuat AlignmentRot = FQuat::FindBetweenNormals(WristForwardLS_UE, OpenXRForwardDirection); + + // Rotate about the aligned forward direction to make the side directions align + FVector WristSideDirectionMS_UE = AlignmentRot * WristSideDirectionLS; + + // Rotate around, should the Side direction flip for openXR if its the left hand? + FQuat TwistRotation = CalcRotationAboutAxis(WristSideDirectionMS_UE, OpenXRSideDirection, OpenXRForwardDirection); + + FRotator Difference = (TwistRotation * AlignmentRot).Rotator(); + + MappedBonePairs.AdjustmentQuat = (TwistRotation * AlignmentRot).GetNormalized(); + +} + +void FAnimNode_ApplyOpenXRHandPose::ConvertHandTransformsSpace(TArray& OutTransforms, const TArray& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones) +{ + // Fail if the count is too low + if (WorldTransforms.Num() < EHandKeypointCount) + return; + + if (OutTransforms.Num() < WorldTransforms.Num()) + { + OutTransforms.Empty(WorldTransforms.Num()); + OutTransforms.AddUninitialized(WorldTransforms.Num()); + } + + TArray TempWorldTransforms = WorldTransforms; + + // Ensure add trans is normalized + AddTrans.NormalizeRotation(); + + // Bone/Parent map + int32 BoneParents[26] = + { + // Manually build the parent hierarchy starting at the wrist which has no parent (-1) + 1, // Palm -> Wrist + -1, // Wrist -> None + 1, // ThumbMetacarpal -> Wrist + 2, // ThumbProximal -> ThumbMetacarpal + 3, // ThumbDistal -> ThumbProximal + 4, // ThumbTip -> ThumbDistal + + 1, // IndexMetacarpal -> Wrist + 6, // IndexProximal -> IndexMetacarpal + 7, // IndexIntermediate -> IndexProximal + 8, // IndexDistal -> IndexIntermediate + 9, // IndexTip -> IndexDistal + + 1, // MiddleMetacarpal -> Wrist + 11, // MiddleProximal -> MiddleMetacarpal + 12, // MiddleIntermediate -> MiddleProximal + 13, // MiddleDistal -> MiddleIntermediate + 14, // MiddleTip -> MiddleDistal + + 1, // RingMetacarpal -> Wrist + 16, // RingProximal -> RingMetacarpal + 17, // RingIntermediate -> RingProximal + 18, // RingDistal -> RingIntermediate + 19, // RingTip -> RingDistal + + 1, // LittleMetacarpal -> Wrist + 21, // LittleProximal -> LittleMetacarpal + 22, // LittleIntermediate -> LittleProximal + 23, // LittleDistal -> LittleIntermediate + 24, // LittleTip -> LittleDistal + }; + + bool bUseAutoCalculatedRetarget = AddTrans.Equals(FTransform::Identity); + + // Convert transforms to parent space + // The hand tracking transforms are in world space. + + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + if (TempWorldTransforms[Index].ContainsNaN() || TempWorldTransforms[Index].Equals(FTransform::Identity)) + { + OutTransforms[Index] = FTransform::Identity; + //continue; + } + + // Ensure normalization + TempWorldTransforms[Index].NormalizeRotation(); + + if (bMirrorLeftRight) + { + TempWorldTransforms[Index].Mirror(EAxis::Y, EAxis::Y); + } + + if (bUseAutoCalculatedRetarget) + { + TempWorldTransforms[Index].ConcatenateRotation(MappedBonePairs.AdjustmentQuat); + //WorldTransforms[Index].ConcatenateRotation(MappedBonePairs.BonePairs[0].RetargetRot); + } + else + { + TempWorldTransforms[Index].ConcatenateRotation(AddTrans.GetRotation()); + } + } + + // Make this into a single loop, their structure always has children after parent + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + FTransform& BoneTransform = TempWorldTransforms[Index]; + //BoneTransform.NormalizeRotation(); + + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + // Thumb keeps the metacarpal intact, we don't skip it + if (bMergeMissingUE4Bones) + { + if (Index != (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT && ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + } + + if (ParentIndex < 0) + { + // We are at the root, so use it. + OutTransforms[Index] = BoneTransform; + } + else + { + FTransform ParentTransform = FTransform::Identity; + + // Merging missing metacarpal bone into the transform + if (bMergeMissingUE4Bones && ParentParent == 1) // Wrist + { + ParentTransform = TempWorldTransforms[ParentParent]; + } + else + { + ParentTransform = TempWorldTransforms[ParentIndex]; + } + + //ParentTransform.NormalizeRotation(); + OutTransforms[Index] = BoneTransform.GetRelativeTransform(ParentTransform); + } + } +} + +void FAnimNode_ApplyOpenXRHandPose::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) +{ + if (!MappedBonePairs.bInitialized) + return; + + + const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); + + UObject* OwningAsset = BoneContainer.GetAsset(); + if (!OwningAsset) + return; + + // Trigger a full re-build if our asset or target skeleton changed, do it up here before finding the correct hand + if ((OwningAsset->GetFName() != MappedBonePairs.LastInitializedName || SkeletonType != MappedBonePairs.LastInitializedSkeleton)) + { + InitializeBoneReferences(BoneContainer); + } + + + /*const */FBPOpenXRActionSkeletalData *StoredActionInfoPtr = nullptr; + if (bIsOpenInputAnimationInstance) + { + /*const*/ FOpenXRAnimInstanceProxy* OpenXRAnimInstance = (FOpenXRAnimInstanceProxy*)Output.AnimInstanceProxy; + if (OpenXRAnimInstance->HandSkeletalActionData.Num()) + { + for (int i = 0; i HandSkeletalActionData.Num(); ++i) + { + EVRSkeletalHandIndex TargetHand = OpenXRAnimInstance->HandSkeletalActionData[i].TargetHand; + + if (OpenXRAnimInstance->HandSkeletalActionData[i].bMirrorLeftRight) + { + TargetHand = (TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + } + + if (TargetHand == MappedBonePairs.TargetHand) + { + StoredActionInfoPtr = &OpenXRAnimInstance->HandSkeletalActionData[i]; + break; + } + } + } + } + + // If we have an empty hand pose but have a passed in custom one then use that + if (StoredActionInfoPtr == nullptr || !StoredActionInfoPtr->SkeletalTransforms.Num()) + { + StoredActionInfoPtr = &OptionalStoredActionInfo; + } + + if (!StoredActionInfoPtr->bHasValidData) + { + return; + } + + //MappedBonePairs.AdjustmentQuat = WristAdjustment; + + // Currently not blending correctly + const float BlendWeight = FMath::Clamp(ActualAlpha, 0.f, 1.f); + uint8 BoneTransIndex = 0; + uint8 NumBones = StoredActionInfoPtr ? StoredActionInfoPtr->SkeletalTransforms.Num() : 0; + + if (NumBones < 1) + { + // Early out, we don't have a valid data to work with + return; + } + + + + + FTransform trans = FTransform::Identity; + OutBoneTransforms.Reserve(MappedBonePairs.BonePairs.Num()); + TArray TransBones; + FTransform AdditionTransform = StoredActionInfoPtr->AdditionTransform; + + FTransform TempTrans = FTransform::Identity; + FTransform ParentTrans = FTransform::Identity; + FTransform * ParentTransPtr = nullptr; + + //AdditionTransform.SetRotation(MappedBonePairs.AdjustmentQuat); + + TArray HandTransforms; + ConvertHandTransformsSpace(HandTransforms, StoredActionInfoPtr->SkeletalTransforms, AdditionTransform, StoredActionInfoPtr->bMirrorLeftRight, MappedBonePairs.bMergeMissingBonesUE4); + + for (const FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs) + { + BoneTransIndex = (int8)BonePair.OpenXRBone; + ParentTrans = FTransform::Identity; + + if (bSkipRootBone && BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT) + continue; + + if (BoneTransIndex >= NumBones || BonePair.ReferenceToConstruct.CachedCompactPoseIndex == INDEX_NONE) + continue; + + if (!BonePair.ReferenceToConstruct.IsValidToEvaluate(BoneContainer)) + { + continue; + } + + trans = Output.Pose.GetComponentSpaceTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + + if (BonePair.ParentReference != INDEX_NONE) + { + ParentTrans = Output.Pose.GetComponentSpaceTransform(BonePair.ParentReference); + ParentTrans.SetScale3D(FVector(1.f)); + } + + EXRHandJointType CurrentBone = (EXRHandJointType)BoneTransIndex; + TempTrans = (HandTransforms[BoneTransIndex]); + //TempTrans.ConcatenateRotation(BonePair.RetargetRot); + + /*if (StoredActionInfoPtr->bMirrorHand) + { + FMatrix M = TempTrans.ToMatrixWithScale(); + M.Mirror(EAxis::Z, EAxis::X); + M.Mirror(EAxis::X, EAxis::Z); + TempTrans.SetFromMatrix(M); + }*/ + + TempTrans = TempTrans * ParentTrans; + + if (StoredActionInfoPtr->bAllowDeformingMesh || bOnlyApplyWristTransform) + trans.SetTranslation(TempTrans.GetTranslation()); + + trans.SetRotation(TempTrans.GetRotation()); + + TransBones.Add(FBoneTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex, trans)); + + // Need to do it per bone so future bones are correct + // Only if in parent space though, can do it all at the end in component space + if (TransBones.Num()) + { + Output.Pose.LocalBlendCSBoneTransforms(TransBones, BlendWeight); + TransBones.Reset(); + } + + if (bOnlyApplyWristTransform && CurrentBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT) + { + break; // Early out of the loop, we only wanted to apply the wrist + } + } +} + +bool FAnimNode_ApplyOpenXRHandPose::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) +{ + return(/*MappedBonePairs.bInitialized && */MappedBonePairs.BonePairs.Num() > 0); +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp new file mode 100644 index 0000000..89c9aa1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp @@ -0,0 +1,587 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "OpenXRExpansionFunctionLibrary.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(OpenXRExpansionFunctionLibrary) + +//#include "EngineMinimal.h" +#include "Engine/Engine.h" +#include +#include "CoreMinimal.h" +#include "IXRTrackingSystem.h" + +//General Log +DEFINE_LOG_CATEGORY(OpenXRExpansionFunctionLibraryLog); + +UOpenXRExpansionFunctionLibrary::UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +//============================================================================= +UOpenXRExpansionFunctionLibrary::~UOpenXRExpansionFunctionLibrary() +{ + +} + +void UOpenXRExpansionFunctionLibrary::GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch& Result) +{ +#if defined(OPENXR_SUPPORTED) + DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController; + Result = EBPXRResultSwitch::OnFailed; + + if (FOpenXRHMD* pOpenXRHMD = GetOpenXRHMD()) + { + XrInstance XRInstance = pOpenXRHMD->GetInstance(); + XrSystemId XRSysID = pOpenXRHMD->GetSystem(); + + if (XRSysID && XRInstance) + { + XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES }; + systemProperties.next = nullptr; + + if (xrGetSystemProperties(XRInstance, XRSysID, &systemProperties) == XR_SUCCESS) + { + XrSession XRSesh = pOpenXRHMD->GetSession(); + + if (XRSesh) + { + XrPath myPath; + XrResult PathResult = xrStringToPath(XRInstance, "/user/hand/left", &myPath); + XrInteractionProfileState interactionProfile{ XR_TYPE_INTERACTION_PROFILE_STATE }; + interactionProfile.next = nullptr; + + XrResult QueryResult = xrGetCurrentInteractionProfile(XRSesh, myPath, &interactionProfile); + if (QueryResult == XR_SUCCESS) + { + char myPathy[XR_MAX_SYSTEM_NAME_SIZE]; + uint32_t outputsize; + xrPathToString(XRInstance, interactionProfile.interactionProfile, XR_MAX_SYSTEM_NAME_SIZE, &outputsize, myPathy); + + if (interactionProfile.interactionProfile == XR_NULL_PATH || outputsize < 1) + return; + + FString InteractionName(ANSI_TO_TCHAR(myPathy)); + if (InteractionName.Len() < 1) + return; + + /* + * Interaction profile paths [6.4] + An interaction profile identifies a collection of buttons and + other input sources, and is of the form: + /interaction_profiles// + Paths supported in the core 1.0 release + /interaction_profiles/khr/simple_control + /interaction_profiles/khr/simple_controller + /interaction_profiles/google/daydream_controller + /interaction_profiles/htc/vive_controller + /interaction_profiles/htc/vive_pro + /interaction_profiles/microsoft/motion_controller + /interaction_profiles/microsoft/xbox_controller + /interaction_profiles/oculus/go_controller + /interaction_profiles/oculus/touch_controller + /interaction_profiles/valve/index_controller + */ + + // Not working currently? + /*XrInputSourceLocalizedNameGetInfo InputSourceInfo{ XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO }; + InputSourceInfo.next = nullptr; + InputSourceInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT; + InputSourceInfo.sourcePath = interactionProfile.interactionProfile; + + char buffer[XR_MAX_SYSTEM_NAME_SIZE]; + uint32_t usedBufferCount = 0; + if (xrGetInputSourceLocalizedName(XRSesh, &InputSourceInfo, XR_MAX_SYSTEM_NAME_SIZE, &usedBufferCount, (char*)&buffer) == XR_SUCCESS) + { + int g = 0; + }*/ + + + if (InteractionName.Find("touch_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_OculusTouchController; + } + else if (InteractionName.Contains("index_controller", ESearchCase::IgnoreCase)) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ValveIndexController; + } + else if (InteractionName.Find("vive_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ViveController; + } + else if (InteractionName.Find("vive_pro", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ViveProController; + } + else if (InteractionName.Find("simple_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_SimpleController; + } + else if (InteractionName.Find("daydream_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_DaydreamController; + } + else if (InteractionName.Find("motion_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftMotionController; + } + else if (InteractionName.Find("xbox_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftXboxController; + } + else if (InteractionName.Find("go_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_OculusGoController; + } + else if (InteractionName.Find("neo3_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_PicoNeo3Controller; + } + else if (InteractionName.Find("mixed_reality_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_WMRController; + } + else + { + UE_LOG(OpenXRExpansionFunctionLibraryLog, Warning, TEXT("UNKNOWN OpenXR Interaction profile detected!!!: %s"), *InteractionName); + DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController; + } + + Result = EBPXRResultSwitch::OnSucceeded; + } + + TrackingSystemName = FString(ANSI_TO_TCHAR(systemProperties.systemName));// , XR_MAX_SYSTEM_NAME_SIZE); + //VendorID = systemProperties.vendorId; + return; + } + } + } + } +#endif + + TrackingSystemName.Empty(); + return; +} + +bool UOpenXRExpansionFunctionLibrary::GetOpenXRHandPose(FBPOpenXRActionSkeletalData& HandPoseContainer, UOpenXRHandPoseComponent* HandPoseComponent, bool bGetMockUpPose) +{ + FXRMotionControllerData MotionControllerData; + + if (bGetMockUpPose) + { + GetMockUpControllerData(MotionControllerData, HandPoseContainer); + return true; + } + + UHeadMountedDisplayFunctionLibrary::GetMotionControllerData((UObject*)HandPoseComponent, HandPoseContainer.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left ? EControllerHand::Left : EControllerHand::Right, MotionControllerData); + + if (MotionControllerData.bValid) + { + HandPoseContainer.SkeletalTransforms.Empty(MotionControllerData.HandKeyPositions.Num()); + FTransform ParentTrans = FTransform::Identity; + + if (MotionControllerData.DeviceVisualType == EXRVisualType::Controller) + { + ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f)); + } + else // EXRVisualType::Hand visual type + { + ParentTrans = FTransform(MotionControllerData.HandKeyRotations[(uint8)EHandKeypoint::Palm], MotionControllerData.HandKeyPositions[(uint8)EHandKeypoint::Palm], FVector(1.f)); + } + + for (int i = 0; i < MotionControllerData.HandKeyPositions.Num(); ++i) + { + // Convert to component space, we convert then to parent space later when applying it + HandPoseContainer.SkeletalTransforms.Add(FTransform(MotionControllerData.HandKeyRotations[i].GetNormalized(), MotionControllerData.HandKeyPositions[i], FVector(1.f)).GetRelativeTransform(ParentTrans)); + } + + //if (bGetCurlValues) + { + GetFingerCurlValues(HandPoseContainer.SkeletalTransforms, HandPoseContainer.FingerCurls); + } + + HandPoseContainer.bHasValidData = (HandPoseContainer.SkeletalTransforms.Num() == EHandKeypointCount); + return true; + } + + HandPoseContainer.bHasValidData = false; + return false; +} + +void UOpenXRExpansionFunctionLibrary::GetFingerCurlValues(TArray& TransformArray, TArray& CurlArray) +{ + // Fail if the count is too low + if (TransformArray.Num() < EHandKeypointCount) + return; + + if (CurlArray.Num() < 5) + { + CurlArray.AddZeroed(5); + } + + CurlArray[0] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::ThumbMetacarpal); + CurlArray[1] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::IndexProximal); + CurlArray[2] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::MiddleProximal); + CurlArray[3] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::RingProximal); + CurlArray[4] = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::LittleProximal); +} + +bool UOpenXRExpansionFunctionLibrary::GetOpenXRFingerCurlValuesForHand( + UObject* WorldContextObject, + EControllerHand TargetHand, + float& ThumbCurl, + float& IndexCurl, + float& MiddleCurl, + float& RingCurl, + float& PinkyCurl) +{ + FXRMotionControllerData MotionControllerData; + UHeadMountedDisplayFunctionLibrary::GetMotionControllerData(WorldContextObject, TargetHand, MotionControllerData); + + // Fail if the count is too low + if (MotionControllerData.HandKeyPositions.Num() < EHandKeypointCount) + return false; + + FTransform ParentTrans = FTransform::Identity; + + if (MotionControllerData.DeviceVisualType == EXRVisualType::Controller) + { + ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f)); + } + else // EXRVisualType::Hand visual type + { + ParentTrans = FTransform(MotionControllerData.HandKeyRotations[(uint8)EHandKeypoint::Palm], MotionControllerData.HandKeyPositions[(uint8)EHandKeypoint::Palm], FVector(1.f)); + } + + TArray TransformArray; + TransformArray.AddUninitialized(MotionControllerData.HandKeyPositions.Num()); + + for (int i = 0; i < MotionControllerData.HandKeyPositions.Num(); ++i) + { + // Convert to component space, we convert then to parent space later when applying it + TransformArray[i] = FTransform(MotionControllerData.HandKeyRotations[i].GetNormalized(), MotionControllerData.HandKeyPositions[i], FVector(1.f)).GetRelativeTransform(ParentTrans); + } + + ThumbCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::ThumbMetacarpal); + IndexCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::IndexProximal); + MiddleCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::MiddleProximal); + RingCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::RingProximal); + PinkyCurl = GetCurlValueForBoneRoot(TransformArray, EHandKeypoint::LittleProximal); + + return true; +} + +float UOpenXRExpansionFunctionLibrary::GetCurlValueForBoneRoot(TArray& TransformArray, EHandKeypoint RootBone) +{ + float Angle1 = 0.0f; + float Angle2 = 0.0f; + float Angle1Curl = 0.0f; + float Angle2Curl = 0.0f; + + if (RootBone == EHandKeypoint::ThumbMetacarpal) + { + FVector Prox = TransformArray[(uint8)RootBone].GetRotation().GetForwardVector(); + FVector Inter = TransformArray[(uint8)RootBone + 1].GetRotation().GetForwardVector(); + FVector Distal = TransformArray[(uint8)RootBone + 2].GetRotation().GetForwardVector(); + + Prox = FVector::VectorPlaneProject(Prox, FVector::UpVector); + Inter = FVector::VectorPlaneProject(Inter, FVector::UpVector); + Distal = FVector::VectorPlaneProject(Distal, FVector::UpVector); + + Angle1 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Inter, Distal))); + Angle2 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Prox, Inter))); + + Angle1Curl = (Angle1 - 10.0f) / 64.0f; + Angle2Curl = (Angle2 - 20.0f) / 42.0f; + } + else + { + FVector Prox = TransformArray[(uint8)RootBone].GetRotation().GetForwardVector(); + FVector Inter = TransformArray[(uint8)RootBone + 1].GetRotation().GetForwardVector(); + FVector Distal = TransformArray[(uint8)RootBone + 2].GetRotation().GetForwardVector(); + + + // We don't use the Y (splay) value, only X and Z plane + + Prox = FVector::VectorPlaneProject(Prox, FVector::RightVector); + Inter = FVector::VectorPlaneProject(Inter, FVector::RightVector); + Distal = FVector::VectorPlaneProject(Distal, FVector::RightVector); + + Angle1 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Inter, Distal))); + Angle2 = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(Prox, Inter))); + + Angle1Curl = (Angle1 - 10.0f) / 60.0f; + Angle2Curl = (Angle2 - 10.0f) / 100.0f; + } + + // Can lower number of variables by doing these + + float FinalAngleAvg = FMath::Clamp((Angle1Curl + Angle2Curl) / 2.0f, 0.0f, 1.0f); + + //GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Finger Curl %f"), IndexCurl)); + return FinalAngleAvg; + +} + +void UOpenXRExpansionFunctionLibrary::ConvertHandTransformsSpaceAndBack(TArray& OutTransforms, const TArray& WorldTransforms) +{ + // Fail if the count is too low + if (WorldTransforms.Num() < EHandKeypointCount) + return; + + if (OutTransforms.Num() < WorldTransforms.Num()) + { + OutTransforms.Empty(WorldTransforms.Num()); + OutTransforms.AddUninitialized(WorldTransforms.Num()); + } + + // Bone/Parent map + int32 BoneParents[26] = + { + // Manually build the parent hierarchy starting at the wrist which has no parent (-1) + 1, // Palm -> Wrist + -1, // Wrist -> None + 1, // ThumbMetacarpal -> Wrist + 2, // ThumbProximal -> ThumbMetacarpal + 3, // ThumbDistal -> ThumbProximal + 4, // ThumbTip -> ThumbDistal + + 1, // IndexMetacarpal -> Wrist + 6, // IndexProximal -> IndexMetacarpal + 7, // IndexIntermediate -> IndexProximal + 8, // IndexDistal -> IndexIntermediate + 9, // IndexTip -> IndexDistal + + 1, // MiddleMetacarpal -> Wrist + 11, // MiddleProximal -> MiddleMetacarpal + 12, // MiddleIntermediate -> MiddleProximal + 13, // MiddleDistal -> MiddleIntermediate + 14, // MiddleTip -> MiddleDistal + + 1, // RingMetacarpal -> Wrist + 16, // RingProximal -> RingMetacarpal + 17, // RingIntermediate -> RingProximal + 18, // RingDistal -> RingIntermediate + 19, // RingTip -> RingDistal + + 1, // LittleMetacarpal -> Wrist + 21, // LittleProximal -> LittleMetacarpal + 22, // LittleIntermediate -> LittleProximal + 23, // LittleDistal -> LittleIntermediate + 24, // LittleTip -> LittleDistal + }; + + // Convert transforms to parent space + // The hand tracking transforms are in world space. + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + FTransform BoneTransform = WorldTransforms[Index]; + BoneTransform.NormalizeRotation(); + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + if (ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + + if (ParentIndex < 0) + { + // We are at the root, so use it. + OutTransforms[Index] = BoneTransform; + } + else + { + + FTransform ParentTransform = FTransform::Identity; + + // Merging missing metacarpal bone into the transform + if (ParentParent == 1) // Wrist + { + ParentTransform = WorldTransforms[ParentParent]; + } + else + { + ParentTransform = WorldTransforms[ParentIndex]; + } + + ParentTransform.NormalizeRotation(); + OutTransforms[Index] = BoneTransform.GetRelativeTransform(ParentTransform); + } + } + + // Check on the easy component space conversion first + { + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + const FTransform& BoneTransform = WorldTransforms[Index]; + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + if (ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + + if (ParentIndex > 0) + { + if (ParentParent == 1) + { + OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentParent]; + } + else + { + OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentIndex]; + } + } + } + } +} + +void UOpenXRExpansionFunctionLibrary::GetMockUpControllerData(FXRMotionControllerData& MotionControllerData, FBPOpenXRActionSkeletalData& SkeletalMappingData, bool bOpenHand) +{ + + + TArray HandRotationsClosed = { + // Closed palm + FQuat(-6.9388939039072284e-18f,-2.7755575615628914e-17f,-5.5511151231257827e-17f,1.0000000181623150), + FQuat(0.0010158333104005046f,-0.031842494413126823f,0.0082646248419453450f,-0.99945823120983279), + FQuat(0.49284713488980170f,-0.22607130273287540f,-0.44054329019960731f,0.71548243409346490), + FQuat(-0.60572821120045273f,-0.017497510794223320f,0.10943807503774633f,-0.78791529705201735), + FQuat(-0.61281359708005512f,-0.23245447006924613f,-0.058766632856478873f,-0.75297472319193537), + FQuat(-0.61281359708005512f,-0.23245447006924613f,-0.058766632856478873f,-0.75297472319193537), + FQuat(-0.11514346379358367f,-0.029229397612833233f,-0.076351329575539195f,0.98997885626789228), + FQuat(0.079333339113826437f,-0.72590009974051883f,0.050346047334316274f,-0.68135202233808378), + FQuat(0.0032539550806410245f,-0.97123336736010268f,0.098921247251489333f,0.21658665949113542), + FQuat(-0.064585069112672477f,-0.57963374972897053f,0.075445998290538954f,0.80880246209406348), + FQuat(0.064585069112672477f,0.57963374972897053f,-0.075445998290538954f,-0.80880246209406348), + FQuat(-7.7702472130181111e-08f,9.8656527815210726e-08f,1.3838491007001075e-06f,1.0000000181613493), + FQuat(0.085300231549708214f,-0.74048187833139134f,0.058532016219761618f,-0.66406663653752407), + FQuat(0.011595964719175678f,-0.98786834549923641f,0.099110835214894707f,0.11899052159928070), + FQuat(-0.063530326287074640f,-0.56012988021281451f,0.076145543493668810f,0.82244775363868539), + FQuat(0.030477923994508188f,0.55662337489051250f,-0.098559802475379960f,-0.82433459003921772), + FQuat(-0.025910339872061101f,0.052311401725670628f,0.042953782692297438f,0.99737013210455761), + FQuat(0.11635892750396379f,-0.74717191584145570f,-0.026909776929005647f,-0.65381238012001353), + FQuat(0.098078041656806447f,-0.98532297068866348f,0.071135591008198620f,0.12024601945419003), + FQuat(0.0028091467060491482f,-0.52817558741956572f,0.11864668261714340f,0.84080060571895254), + FQuat(-0.0028091467060491482f,0.52817558741956572f,-0.11864668261714340f,-0.84080060571895254), + FQuat(-0.11913260527111892f,0.10934177100849747f,0.11664955310670821f,0.97992077106196507), + FQuat(-0.18696185363314571f,0.78123174637415782f,0.043590203318890380f,0.59398834520762267), + FQuat(0.15903913486884827f,-0.97287570092949460f,0.091728860868847406f,0.14073122087670015), + FQuat(0.035141532005084519f,-0.48251853052338572f,0.18910886397987722f,0.85450501128943579), + FQuat(0.035141532005084519f,-0.48251853052338572f,0.18910886397987722f,0.85450501128943579) + }; + TArray HandRotationsOpen = { + // Open Hand + FQuat(0.167000905f,-0.670308471f,0.304047525f,-0.656011939f), + FQuat(-0.129862994f,0.736467659f,-0.315623045f,0.584065497f), + FQuat(-0.030090153f,0.121532254f,-0.840178490f,0.527659237f), + FQuat(-0.126470163f,-0.262596279f,0.816956878f,-0.497623593f), + FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f), + FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f), + FQuat(-0.277370781f,0.686735749f,-0.258646101f,0.620130479f), + FQuat(0.193366051f,-0.808131576f,0.260262072f,-0.491728455f), + FQuat(0.145547777f,-0.854364336f,0.317562312f,-0.384749293f), + FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f), + FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f), + FQuat(0.166999936f,-0.670307159f,0.304047883f,-0.656013489f), + FQuat(0.206125781f,-0.815250278f,0.203173012f,-0.501596987f), + FQuat(0.164493740f,-0.890369833f,0.257293820f,-0.337613612f), + FQuat(0.114019498f,-0.937856555f,0.283619940f,-0.164267495f), + FQuat(0.107336335f,-0.925720870f,0.321023613f,-0.168710276f), + FQuat(0.156629071f,-0.719596088f,0.210793152f,-0.642817795f), + FQuat(0.194258988f,-0.858762920f,0.127883837f,-0.456546605f), + FQuat(0.166189745f,-0.930981100f,0.161785051f,-0.281922638f), + FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f), + FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f), + FQuat(0.160288095f,-0.812664807f,0.100923792f,-0.551087677f), + FQuat(0.207311243f,-0.870556056f,0.056644741f,-0.442656904f), + FQuat(0.191506147f,-0.944826961f,0.096772499f,-0.247511998f), + FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f), + FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f) + }; + + MotionControllerData.HandKeyRotations = /*SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandRotationsOpen :*/ HandRotationsClosed; + + TArray HandPositionsClosed = { + // Closed palm - Left + FVector(0.0000000000000000f,0.0000000000000000f,0.0000000000000000f), + FVector(-2.8690212431406792f,0.70708009295073815f,-0.47404338536985718f), + FVector(-1.1322360817697272f,-2.1125772981974671f,-1.2278703475596775f), + FVector(0.92697682070144727f,-5.5601677377459957f,-1.6753327187355360f), + FVector(4.0987554778339428f,-6.0520138168462640f,-2.1960898756852747f), + FVector(5.5854053809842918f,-5.4247506634349065f,-2.6631245417791525f), + FVector(-1.6026417502203387f,-1.4646945203797794f,-0.17057236434820122f), + FVector(5.7352432311721007f,-2.5389617545260998f,0.39061644722637634f), + FVector(5.4801464829170561f,-3.3344912297783416f,-3.8566611419550343f), + FVector(2.9179605371815693f,-3.2311985822561073f,-2.6652727318443148f), + FVector(3.2708935578922342f,-3.0117453368521279f,-1.6311186720587312f), + FVector(-1.1935619377191149f,-1.8034793735494103e-05f,1.4147048846974153e-06f), + FVector(5.9028526610092893f,1.3513666817788206e-05f,-4.3170989212359956e-06f), + FVector(5.4567759872551527f,-0.87968929643487392f,-4.1965100581882382f), + FVector(2.2252652348065158f,-0.87742006725177069f,-3.4067970851427791f), + FVector(2.6916877869696085f,-0.62360084690574125f,-2.2285708116727738f), + FVector(-1.1796822503165614f,1.3653411443775685f,-0.17694011615865479f), + FVector(5.3502831208188670f,1.9121382570769896f,-0.87930289382919313f), + FVector(4.8743654830862742f,1.3526757302541959f,-4.8457258101076217f), + FVector(2.1622015314362244f,0.85068796311660544f,-4.1307752690132205f), + FVector(2.6021369184528194f,1.0596020074600654f,-3.1860412064934174f), + FVector(-1.2905361603753163f,2.6108535683555365f,-0.46423293549223010f), + FVector(4.6820094577722964f,3.8858425146699327f,-1.9880098921962746f), + FVector(4.0115118130532306f,3.1678700881616777f,-4.8092930847360869f), + FVector(2.3757993445967389f,2.6579252395479291f,-4.2645319235961239f), + FVector(2.7329133227289351f,2.8811366857469527f,-3.6179750674182261f) + }; + + // Open Hand + TArray HandPositionsOpen = { + FVector(-1014.001f,-478.278f,212.902f), + FVector(-1013.516f,-476.006f,214.688f), + FVector(-1016.362f,-479.642f,215.119f), + FVector(-1018.145f,-483.254f,214.805f), + FVector(-1019.682f,-485.682f,213.284f), + FVector(-1020.480f,-486.982f,212.581f), + FVector(-1014.360f,-478.927f,215.169f), + FVector(-1014.932f,-484.146f,209.902f), + FVector(-1016.872f,-486.643f,206.852f), + FVector(-1018.771f,-488.058f,205.231f), + FVector(-1019.613f,-488.507f,204.655f), + FVector(-1013.901f,-477.534f,213.831f), + FVector(-1014.494f,-481.954f,208.310f), + FVector(-1016.269f,-484.282f,205.146f), + FVector(-1018.657f,-485.834f,203.427f), + FVector(-1019.846f,-486.231f,203.113f), + FVector(-1013.816f,-476.436f,213.006f), + FVector(-1014.637f,-479.707f,207.344f), + FVector(-1016.703f,-481.540f,204.355f), + FVector(-1018.962f,-482.692f,203.000f), + FVector(-1019.978f,-482.975f,202.870f), + FVector(-1013.845f,-475.325f,212.363f), + FVector(-1015.993f,-477.665f,206.928f), + FVector(-1017.571f,-478.907f,204.670f), + FVector(-1019.033f,-479.652f,203.887f), + FVector(-1019.778f,-479.842f,203.819f) + }; + + MotionControllerData.HandKeyPositions = /*SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandPositionsOpen : */HandPositionsClosed; + + if (SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left) + { + MotionControllerData.GripPosition = FVector(-1018.305f, -478.019f, 209.872f); + MotionControllerData.GripRotation = FQuat(-0.116352126f, 0.039430488f, -0.757644236f, 0.641001403f); + } + else + { + MotionControllerData.GripPosition = FVector(-1202.619f, -521.077f, 283.076f); + MotionControllerData.GripRotation = FQuat(0.040843058f, 0.116659224f, 0.980030060f, -0.155767411f); + } + + MotionControllerData.DeviceName = TEXT("OpenXR"); + + SkeletalMappingData.SkeletalTransforms.Empty(SkeletalMappingData.SkeletalTransforms.Num()); + FTransform ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f)); + for (int i = 0; i < MotionControllerData.HandKeyPositions.Num(); i++) + { + SkeletalMappingData.SkeletalTransforms.Add(FTransform(MotionControllerData.HandKeyRotations[i], MotionControllerData.HandKeyPositions[i], FVector(1.f)).GetRelativeTransform(ParentTrans)); + } + + SkeletalMappingData.bHasValidData = (SkeletalMappingData.SkeletalTransforms.Num() == EHandKeypointCount); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp new file mode 100644 index 0000000..47fac71 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp @@ -0,0 +1,24 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "OpenXRExpansionPlugin.h" +#include "OpenXRExpansionFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "FXRExpansionPluginModule" + +void FOpenXRExpansionPluginModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + //LoadOpenVRModule(); +} + +void FOpenXRExpansionPluginModule::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. +// UnloadOpenVRModule(); +} + + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FOpenXRExpansionPluginModule, OpenXRExpansionPlugin) \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp new file mode 100644 index 0000000..0912c8d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp @@ -0,0 +1,884 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "OpenXRHandPoseComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(OpenXRHandPoseComponent) + +#include "Net/UnrealNetwork.h" +#include "MotionControllerComponent.h" +#include "OpenXRExpansionFunctionLibrary.h" +#include "Engine/NetSerialization.h" + +#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName() +//#include "EngineMinimal.h" + +UOpenXRHandPoseComponent::UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + ReplicationRateForSkeletalAnimations = 10.f; + bReplicateSkeletalData = false; + bSmoothReplicatedSkeletalData = true; + SkeletalNetUpdateCount = 0.f; + bDetectGestures = true; + SetIsReplicatedByDefault(true); + bGetMockUpPoseForDebugging = false; +} + +void UOpenXRHandPoseComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UOpenXRHandPoseComponent, LeftHandRep, COND_SkipOwner); + DOREPLIFETIME_CONDITION(UOpenXRHandPoseComponent, RightHandRep, COND_SkipOwner); +} + +void UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Implementation(const FBPXRSkeletalRepContainer& SkeletalInfo) +{ + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == SkeletalInfo.TargetHand) + { + if (SkeletalInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + if (bSmoothReplicatedSkeletalData) + { + LeftHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + + FBPXRSkeletalRepContainer::CopyReplicatedTo(SkeletalInfo, HandSkeletalActions[i]); + LeftHandRep = SkeletalInfo; + + if (bSmoothReplicatedSkeletalData) + { + LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + } + else + { + if (bSmoothReplicatedSkeletalData) + { + RightHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + + FBPXRSkeletalRepContainer::CopyReplicatedTo(SkeletalInfo, HandSkeletalActions[i]); + RightHandRep = SkeletalInfo; + + if (bSmoothReplicatedSkeletalData) + { + RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + } + + break; + } + } +} + +bool UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Validate(const FBPXRSkeletalRepContainer& SkeletalInfo) +{ + return true; +} + +void FOpenXRAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) +{ + Super::PreUpdate(InAnimInstance, DeltaSeconds); + + if (UOpenXRAnimInstance* OwningInstance = Cast(InAnimInstance)) + { + if (OwningInstance->OwningPoseComp) + { + if (HandSkeletalActionData.Num() != OwningInstance->OwningPoseComp->HandSkeletalActions.Num()) + { + HandSkeletalActionData.Empty(OwningInstance->OwningPoseComp->HandSkeletalActions.Num()); + + for(FBPOpenXRActionSkeletalData& actionInfo : OwningInstance->OwningPoseComp->HandSkeletalActions) + { + HandSkeletalActionData.Add(actionInfo); + } + } + else + { + for (int i = 0; i < OwningInstance->OwningPoseComp->HandSkeletalActions.Num(); ++i) + { + HandSkeletalActionData[i] = OwningInstance->OwningPoseComp->HandSkeletalActions[i]; + } + } + } + } +} + +FOpenXRAnimInstanceProxy::FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance) + : FAnimInstanceProxy(InAnimInstance) +{ +} + +void UOpenXRHandPoseComponent::BeginPlay() +{ + /*if (UMotionControllerComponent * MotionParent = Cast(GetAttachParent())) + { + EControllerHand HandType; + if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionParent->MotionSource, HandType)) + { + HandType = EControllerHand::Left; + } + + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand) + HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left; + else + HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + } + + }*/ + + Super::BeginPlay(); +} + +void UOpenXRHandPoseComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!IsLocallyControlled()) + { + if (bReplicateSkeletalData) + { + // Handle bone lerping here if we are replicating + for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions) + { + if (bSmoothReplicatedSkeletalData) + { + if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + LeftHandRepManager.UpdateManager(DeltaTime, actionInfo, this); + } + else + { + RightHandRepManager.UpdateManager(DeltaTime, actionInfo, this); + } + } + } + } + } + else // Get data and process + { + bool bGetCompressedTransforms = false; + if (bReplicateSkeletalData && HandSkeletalActions.Num() > 0) + { + SkeletalNetUpdateCount += DeltaTime; + if (SkeletalNetUpdateCount >= (1.0f / ReplicationRateForSkeletalAnimations)) + { + SkeletalNetUpdateCount = 0.0f; + bGetCompressedTransforms = true; + } + } + + for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions) + { + if (UOpenXRExpansionFunctionLibrary::GetOpenXRHandPose(actionInfo, this, bGetMockUpPoseForDebugging)) + { + if (bGetCompressedTransforms) + { + if (GetNetMode() == NM_Client) + { + if (actionInfo.bHasValidData) + { + FBPXRSkeletalRepContainer ContainerSend; + ContainerSend.CopyForReplication(actionInfo); + Server_SendSkeletalTransforms(ContainerSend); + } + } + else + { + if (actionInfo.bHasValidData) + { + if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + LeftHandRep.CopyForReplication(actionInfo); + else + RightHandRep.CopyForReplication(actionInfo); + } + } + } + } + + if (bDetectGestures && actionInfo.bHasValidData && actionInfo.SkeletalTransforms.Num() > 0 && GesturesDB != nullptr && GesturesDB->Gestures.Num() > 0) + { + DetectCurrentPose(actionInfo); + } + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + +bool UOpenXRHandPoseComponent::SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave) +{ + + if (!HandSkeletalActions.Num()) + return false; + + // Default to the first hand element so that single length arrays work as is. + FBPOpenXRActionSkeletalData* HandSkeletalAction = nullptr; + + // Now check for the specific passed in hand if this is a multi hand + for (int i = 0; i < HandSkeletalActions.Num(); ++i) + { + if (HandSkeletalActions[i].TargetHand == HandToSave) + { + HandSkeletalAction = &HandSkeletalActions[i]; + break; + } + } + + if (!HandSkeletalAction || !HandSkeletalAction->bHasValidData || HandSkeletalAction->SkeletalTransforms.Num() < EHandKeypointCount) + return false; + + if (GesturesDB) + { + FOpenXRGesture NewGesture; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + for (int i = 0; i < 5; ++i) + { + if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc, (EXRHandJointType)FingerMap[i]); + } + else + { + NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc, (EXRHandJointType)FingerMap[i]); + } + } + + NewGesture.Name = RecordingName; + GesturesDB->Gestures.Add(NewGesture); + + return true; + } + + return false; +} + + +bool UOpenXRHandPoseComponent::K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut) +{ + if (!GesturesDB || GesturesDB->Gestures.Num() < 1) + return false; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + // Early fill in an array to keep from performing math for each gesture + TArray CurrentTips; + CurrentTips.AddUninitialized(5); + for (int i = 0; i < 5; ++i) + { + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc; + } + else + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc; + } + } + + for (const FOpenXRGesture& Gesture : GesturesDB->Gestures) + { + // If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it + if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + continue; + + bool bDetectedPose = true; + for (int i = 0; i < 5; ++i) + { + FVector GestureV = Gesture.FingerValues[i].Value; + FVector CurrentV = CurrentTips[i]; + FVector Difference = GestureV - CurrentV; + + if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold)) + { + bDetectedPose = false; + break; + } + } + + if (bDetectedPose) + { + GestureOut = Gesture; + return true; + } + } + + return false; +} + +bool UOpenXRHandPoseComponent::DetectCurrentPose(FBPOpenXRActionSkeletalData &SkeletalAction) +{ + if (!GesturesDB || GesturesDB->Gestures.Num() < 1 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + return false; + + FTransform BoneTransform = FTransform::Identity; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + // Early fill in an array to keep from performing math for each gesture + TArray CurrentTips; + CurrentTips.AddUninitialized(5); + for (int i = 0; i < 5; ++i) + { + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc; + } + else + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc; + } + } + + for (auto GestureIterator = GesturesDB->Gestures.CreateConstIterator(); GestureIterator; ++GestureIterator) + { + const FOpenXRGesture &Gesture = *GestureIterator; + + // If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it + if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + continue; + + bool bDetectedPose = true; + for (int i = 0; i < 5; ++i) + { + if (Gesture.FingerValues[i].Threshold <= 0.0f) + continue; + + if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold)) + { + bDetectedPose = false; + break; + } + } + + if (bDetectedPose) + { + if (SkeletalAction.LastHandGesture != Gesture.Name) + { + if (SkeletalAction.LastHandGesture != NAME_None) + OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + + SkeletalAction.LastHandGesture = Gesture.Name; + SkeletalAction.LastHandGestureIndex = GestureIterator.GetIndex(); + OnNewGestureDetected.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + + return true; + } + else + return false; // Same gesture + } + } + + if (SkeletalAction.LastHandGesture != NAME_None) + { + OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + SkeletalAction.LastHandGesture = NAME_None; + SkeletalAction.LastHandGestureIndex = INDEX_NONE; + } + + return false; +} + +UOpenXRHandPoseComponent::FTransformLerpManager::FTransformLerpManager() +{ + bReplicatedOnce = false; + bLerping = false; + UpdateCount = 0.0f; + UpdateRate = 0.0f; +} + +void UOpenXRHandPoseComponent::FTransformLerpManager::PreCopyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing) +{ + if (!bExponentialSmoothing) + { + if (ActionInfo.SkeletalTransforms.Num()) + { + OldTransforms = ActionInfo.SkeletalTransforms; + } + } +} + +void UOpenXRHandPoseComponent::FTransformLerpManager::NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing) +{ + UpdateRate = (1.0f / NetUpdateRate); + + if (bReplicatedOnce) + { + bLerping = true; + UpdateCount = 0.0f; + NewTransforms = ActionInfo.SkeletalTransforms; + + ActionInfo.SkeletalTransforms = OldTransforms; + + } + else + { + if (bExponentialSmoothing) + { + OldTransforms = ActionInfo.SkeletalTransforms; + } + + bReplicatedOnce = true; + } +} + +void UOpenXRHandPoseComponent::FTransformLerpManager::UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo, UOpenXRHandPoseComponent* ParentComp) +{ + if (!ActionInfo.bHasValidData || !OldTransforms.Num()) + return; + + if (bLerping) + { + bool bExponentialSmoothing = ParentComp->bUseExponentialSmoothing; + float LerpVal = 0.0f; + + if (!bExponentialSmoothing || ParentComp->InterpolationSpeed <= 0.f) + { + UpdateCount += DeltaTime; + + // Keep LerpVal + LerpVal = FMath::Clamp(UpdateCount / UpdateRate, 0.0f, 1.0f); + } + else + { + LerpVal = FMath::Clamp(DeltaTime * ParentComp->InterpolationSpeed, 0.f, 1.f); + } + + if (!bExponentialSmoothing && LerpVal >= 1.0f) + { + bLerping = false; + UpdateCount = 0.0f; + ActionInfo.SkeletalTransforms = NewTransforms; + } + else + { + int32 BoneCountAdjustment = 6 + (ActionInfo.bEnableUE4HandRepSavings ? 4 : 0); + if ((NewTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment)) || (NewTransforms.Num() != ActionInfo.SkeletalTransforms.Num())) + { + return; + } + + ActionInfo.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity; + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + //BlendBone((uint8)EVROpenXRBones::eBone_Thumb3, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, ActionInfo, LerpVal, bExponentialSmoothing); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + // These are copied from the 3rd joints as they use the same transform but a different root + // Don't want to waste cpu time blending these + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_Thumb] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Thumb2]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_IndexFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_IndexFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_MiddleFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_MiddleFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_RingFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_RingFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_PinkyFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_PinkyFinger3]; + } + } +} + +void FBPXRSkeletalRepContainer::CopyForReplication(FBPOpenXRActionSkeletalData& Other) +{ + TargetHand = Other.TargetHand; + + if (!Other.bHasValidData) + return; + + bAllowDeformingMesh = Other.bAllowDeformingMesh; + bEnableUE4HandRepSavings = Other.bEnableUE4HandRepSavings; + + // Instead of doing this, we likely need to lerp but this is for testing + //SkeletalTransforms = Other.SkeletalData.SkeletalTransforms; + + if (Other.SkeletalTransforms.Num() < EHandKeypointCount) + { + SkeletalTransforms.Empty(); + return; + } + + int32 BoneCountAdjustment = 6 + (bEnableUE4HandRepSavings ? 4 : 0); + + if (SkeletalTransforms.Num() != EHandKeypointCount - BoneCountAdjustment) + { + SkeletalTransforms.Reset(EHandKeypointCount - BoneCountAdjustment); // Minus bones we don't need + SkeletalTransforms.AddUninitialized(EHandKeypointCount - BoneCountAdjustment); + } + + int32 idx = 0; + // Root is always identity + //SkeletalTransforms[0] = Other.SkeletalData.SkeletalTransforms[(uint8)EVROpenInputBones::eBone_Root]; // This has no pos right? Need to skip pos on it + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT]; +} + +void FBPXRSkeletalRepContainer::CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other) +{ + int32 BoneCountAdjustment = 6 + (Container.bEnableUE4HandRepSavings ? 4 : 0); + if (Container.SkeletalTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment)) + { + Other.SkeletalTransforms.Empty(); + Other.bHasValidData = false; + return; + } + + Other.bAllowDeformingMesh = Container.bAllowDeformingMesh; + Other.bEnableUE4HandRepSavings = Container.bEnableUE4HandRepSavings; + + // Instead of doing this, we likely need to lerp but this is for testing + //Other.SkeletalData.SkeletalTransforms = Container.SkeletalTransforms; + + if (Other.SkeletalTransforms.Num() != EHandKeypointCount) + { + Other.SkeletalTransforms.Reset(EHandKeypointCount); + Other.SkeletalTransforms.AddUninitialized(EHandKeypointCount); + } + + int32 idx = 0; + + // Only fill in the ones that we care about + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity; // Always identity + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT] = Container.SkeletalTransforms[idx++]; + + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT] = FTransform::Identity; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT] = FTransform::Identity; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT] = FTransform::Identity; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT] = FTransform::Identity; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT] = FTransform::Identity; + + Other.bHasValidData = true; +} + +bool FBPXRSkeletalRepContainer::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + Ar.SerializeBits(&TargetHand, 1); + Ar.SerializeBits(&bAllowDeformingMesh, 1); + Ar.SerializeBits(&bEnableUE4HandRepSavings, 1); + + int32 BoneCountAdjustment = 6 + (bEnableUE4HandRepSavings ? 4 : 0); + uint8 TransformCount = EHandKeypointCount - BoneCountAdjustment; + + bool bHasValidData = SkeletalTransforms.Num() >= TransformCount; + Ar.SerializeBits(&bHasValidData, 1); + + //Ar << TransformCount; + + if (Ar.IsLoading()) + { + SkeletalTransforms.Reset(TransformCount); + } + + FVector Position = FVector::ZeroVector; + FRotator Rot = FRotator::ZeroRotator; + + if (bHasValidData) + { + for (int i = 0; i < TransformCount; i++) + { + if (Ar.IsSaving()) + { + if (bAllowDeformingMesh) + Position = SkeletalTransforms[i].GetLocation(); + + Rot = SkeletalTransforms[i].Rotator(); + } + + if (bAllowDeformingMesh) + bOutSuccess &= SerializePackedVector<10, 11>(Position, Ar); + + Rot.SerializeCompressed(Ar); // Short? 10 bit? + + if (Ar.IsLoading()) + { + if (bAllowDeformingMesh) + SkeletalTransforms.Add(FTransform(Rot, Position)); + else + SkeletalTransforms.Add(FTransform(Rot)); + } + } + } + + return bOutSuccess; +} + +void UOpenXRAnimInstance::NativeBeginPlay() +{ + Super::NativeBeginPlay(); + + AActor* Owner = GetOwningComponent()->GetOwner(); + UActorComponent* HandPoseComp = nullptr; + + if (Owner) + { + HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + + if (!HandPoseComp) + { + // We are also checking owner->owner in case hand mesh is in a sub actor + if (Owner->GetOwner()) + { + HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + } + } + } + + if (!HandPoseComp) + { + return; + } + + if (UOpenXRHandPoseComponent* HandComp = Cast(HandPoseComp)) + { + OwningPoseComp = HandComp; + } +} + +/*void UOpenXRAnimInstance::NativeInitializeAnimation() +{ + Super::NativeInitializeAnimation(); + + AActor* Owner = GetOwningComponent()->GetOwner(); + UActorComponent* HandPoseComp = nullptr; + + if (Owner) + { + HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + + if (!HandPoseComp) + { + // We are also checking owner->owner in case hand mesh is in a sub actor + if (Owner->GetOwner()) + { + HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + } + } + } + + if (!HandPoseComp) + { + return; + } + + if (UOpenXRHandPoseComponent* HandComp = Cast(HandPoseComp)) + { + OwningPoseComp = HandComp; + } +}*/ + +void UOpenXRAnimInstance::InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData& SkeletalMappingData) +{ + USkeleton* AssetSkeleton = this->CurrentSkeleton;//RequiredBones.GetSkeletonAsset(); + + if (AssetSkeleton) + { + FBoneContainer& RequiredBones = this->GetRequiredBones(); + for (FBPOpenXRSkeletalPair& BonePair : SkeletalMappingData.BonePairs) + { + // Fill in the bone name for the reference + BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget; + + // Init the reference + BonePair.ReferenceToConstruct.Initialize(AssetSkeleton); + BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones); + + if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE)) + { + // Get our parent bones index + BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + } + } + + if (UObject* OwningAsset = RequiredBones.GetAsset()) + { + SkeletalMappingData.LastInitializedName = OwningAsset->GetFName(); + } + + SkeletalMappingData.bInitialized = true; + return; + } + + SkeletalMappingData.bInitialized = false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h new file mode 100644 index 0000000..f3868d6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h @@ -0,0 +1,120 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_SkeletalControlBase.h" +#include "OpenXRExpansionTypes.h" +//#include "Skeleton/BodyStateSkeleton.h" +//#include "BodyStateAnimInstance.h" + +#include "AnimNode_ApplyOpenXRHandPose.generated.h" + + +USTRUCT() +struct OPENXREXPANSIONPLUGIN_API FAnimNode_ApplyOpenXRHandPose : public FAnimNode_SkeletalControlBase +{ + GENERATED_USTRUCT_BODY() + +public: + + FVector WristForwardLS_UE; + FVector WristSideDirectionLS; + + // Generally used when not passing in custom bone mappings, defines the auto mapping style + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + EVROpenXRSkeletonType SkeletonType; + + // If your hand is part of a full body or arm skeleton and you don't have a proxy bone to retain the position enable this + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + bool bSkipRootBone; + + // If you only want to use the wrist transform part of this + // This will also automatically add the deform to the wrist as it doesn't make much sense without it + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + bool bOnlyApplyWristTransform; + + // Generally used when not passing in custom bone mappings, defines the auto mapping style + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + FBPOpenXRActionSkeletalData OptionalStoredActionInfo; + + // MappedBonePairs, if you leave it blank then they will auto generate based off of the SkeletonType + // Otherwise, fill out yourself. + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinHiddenByDefault)) + FBPOpenXRSkeletalMappingData MappedBonePairs; + + bool bIsOpenInputAnimationInstance; + + void ConvertHandTransformsSpace(TArray& OutTransforms, const TArray& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones); + + void CalculateSkeletalAdjustment(USkeleton* AssetSkeleton); + void CalculateOpenXRAdjustment(); + + FQuat CalcRotationAboutAxis(const FVector& FromDirection, const FVector& ToDirection, const FVector& Axis) + { + FVector FromDirectionCp = FVector::CrossProduct(Axis, FromDirection); + FVector ToDirectionCp = FVector::CrossProduct(Axis, ToDirection); + + return FQuat::FindBetweenVectors(FromDirectionCp, ToDirectionCp); + } + + FTransform GetRefBoneInCS(TArray& RefBones, TArray& RefBonesInfo, int32 BoneIndex) + { + FTransform BoneTransform; + + if (BoneIndex >= 0) + { + BoneTransform = RefBones[BoneIndex]; + if (RefBonesInfo[BoneIndex].ParentIndex >= 0) + { + BoneTransform *= GetRefBoneInCS(RefBones, RefBonesInfo, RefBonesInfo[BoneIndex].ParentIndex); + } + } + + return BoneTransform; + } + + void SetVectorToMaxElement(FVector& vec) + { + FVector absVal = vec.GetAbs(); + if (absVal.X > absVal.Y && absVal.X > absVal.Z) + { + vec = vec.GetSignVector() * FVector(1.0f, 0.f, 0.f); + vec.Normalize(); + } + else if (absVal.Y > absVal.X && absVal.Y > absVal.Z) + { + vec = vec.GetSignVector() * FVector(0.0f, 1.f, 0.f); + vec.Normalize(); + } + else if (absVal.Z > absVal.X && absVal.Z > absVal.Y) + { + vec = vec.GetSignVector() * FVector(0.0f, 0.f, 1.f); + vec.Normalize(); + } + else + { + vec.Normalize(); + } + } + + // FAnimNode_SkeletalControlBase interface + //virtual void UpdateInternal(const FAnimationUpdateContext& Context) override; + virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) override; + virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; + // End of FAnimNode_SkeletalControlBase interface + virtual void OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance) override; + virtual bool NeedsOnInitializeAnimInstance() const override { return true; } + virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; + + virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; + virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override; + + // Constructor + FAnimNode_ApplyOpenXRHandPose(); + +protected: + bool WorldIsGame; + AActor* OwningActor; + +private: +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h new file mode 100644 index 0000000..dbac085 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h @@ -0,0 +1,102 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "HeadMountedDisplayTypes.h" +#if defined(OPENXR_SUPPORTED) +#include "OpenXRCore.h" +#include "OpenXRHMD.h" +#endif +#include "OpenXRExpansionTypes.h" +#include "HeadMountedDisplayFunctionLibrary.h" + +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" + +#include "OpenXRExpansionFunctionLibrary.generated.h" + +#if !defined(OPENXR_SUPPORTED) +class FOpenXRHMD; +#endif + +DECLARE_LOG_CATEGORY_EXTERN(OpenXRExpansionFunctionLibraryLog, Log, All); + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPOpenXRControllerDeviceType : uint8 +{ + DT_SimpleController, + DT_ValveIndexController, + DT_ViveController, + DT_ViveProController, + //DT_CosmosController, + DT_DaydreamController, + DT_OculusTouchController, + DT_OculusGoController, + DT_MicrosoftMotionController, + DT_MicrosoftXboxController, + DT_PicoNeo3Controller, + DT_WMRController, + DT_UnknownController +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENXREXPANSIONPLUGIN_API UOpenXRExpansionFunctionLibrary : public UBlueprintFunctionLibrary +{ + //GENERATED_BODY() + GENERATED_BODY() + +public: + UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer); + + ~UOpenXRExpansionFunctionLibrary(); +public: + + static FOpenXRHMD* GetOpenXRHMD() + { +#if defined(OPENXR_SUPPORTED) + static FName SystemName(TEXT("OpenXR")); + if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName)) + { + return static_cast(GEngine->XRSystem.Get()); + } +#endif + + return nullptr; + } + + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static bool GetOpenXRHandPose(FBPOpenXRActionSkeletalData& HandPoseContainer, UOpenXRHandPoseComponent* HandPoseComponent, bool bGetMockUpPose = false); + + //UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static void GetFingerCurlValues(TArray& TransformArray, TArray& CurlArray); + + // Get the estimated curl values from hand tracking + // Will return true if it was able to get the curls, false if it could not (hand tracking not enabled or no data for the tracked index) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (WorldContext = "WorldContextObject")) + static bool GetOpenXRFingerCurlValuesForHand( + UObject* WorldContextObject, + EControllerHand TargetHand, + float& ThumbCurl, + float& IndexCurl, + float& MiddleCurl, + float& RingCurl, + float& PinkyCurl); + + static float GetCurlValueForBoneRoot(TArray& TransformArray, EHandKeypoint RootBone); + + //UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static void ConvertHandTransformsSpaceAndBack(TArray& OutTransforms, const TArray& WorldTransforms); + + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static void GetMockUpControllerData(FXRMotionControllerData& MotionControllerData, FBPOpenXRActionSkeletalData& SkeletalMappingData, bool bOpenHand = false); + + // Get a list of all currently tracked devices and their types, index in the array is their device index + // Returns failed if the openXR query failed (no interaction profile yet or openXR is not running) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + static void GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch &Result); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h new file mode 100644 index 0000000..23d057c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h @@ -0,0 +1,18 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + +class FOpenXRExpansionPluginModule : public IModuleInterface +{ +public: + + FOpenXRExpansionPluginModule() + { + } + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h new file mode 100644 index 0000000..c2b7af6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h @@ -0,0 +1,479 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" + +#include "OpenXRExpansionTypes.generated.h" + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBPXRResultSwitch : uint8 +{ + // On Success + OnSucceeded, + // On Failure + OnFailed +}; + +UENUM(BlueprintType) +enum class EVRSkeletalHandIndex : uint8 +{ + EActionHandIndex_Left = 0, + EActionHandIndex_Right +}; + +UENUM(BlueprintType) +enum class EXRHandJointType : uint8 +{ + OXR_HAND_JOINT_PALM_EXT = 0, + OXR_HAND_JOINT_WRIST_EXT = 1, + OXR_HAND_JOINT_THUMB_METACARPAL_EXT = 2, + OXR_HAND_JOINT_THUMB_PROXIMAL_EXT = 3, + OXR_HAND_JOINT_THUMB_DISTAL_EXT = 4, + OXR_HAND_JOINT_THUMB_TIP_EXT = 5, + OXR_HAND_JOINT_INDEX_METACARPAL_EXT = 6, + OXR_HAND_JOINT_INDEX_PROXIMAL_EXT = 7, + OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT = 8, + OXR_HAND_JOINT_INDEX_DISTAL_EXT = 9, + OXR_HAND_JOINT_INDEX_TIP_EXT = 10, + OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT = 11, + OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT = 12, + OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT = 13, + OXR_HAND_JOINT_MIDDLE_DISTAL_EXT = 14, + OXR_HAND_JOINT_MIDDLE_TIP_EXT = 15, + OXR_HAND_JOINT_RING_METACARPAL_EXT = 16, + OXR_HAND_JOINT_RING_PROXIMAL_EXT = 17, + OXR_HAND_JOINT_RING_INTERMEDIATE_EXT = 18, + OXR_HAND_JOINT_RING_DISTAL_EXT = 19, + OXR_HAND_JOINT_RING_TIP_EXT = 20, + OXR_HAND_JOINT_LITTLE_METACARPAL_EXT = 21, + OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT = 22, + OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT = 23, + OXR_HAND_JOINT_LITTLE_DISTAL_EXT = 24, + OXR_HAND_JOINT_LITTLE_TIP_EXT = 25, + OXR_HAND_JOINT_MAX_ENUM_EXT = 0xFF +}; + +UENUM(BlueprintType) +enum class EVROpenXRSkeletonType : uint8 +{ + // UE4 Skeletal Right hand + OXR_SkeletonType_UE4Default_Right, + // UE4 Skeletal Left hand + OXR_SkeletonType_UE4Default_Left, + + // OpenVR Skeletal Right hand + OXR_SkeletonType_OpenVRDefault_Right, + + // OpenVR Skeletal Left hand + OXR_SkeletonType_OpenVRDefault_Left, + + // UE5 Skeletal Right hand + OXR_SkeletonType_UE5Default_Right, + // UE5 Skeletal Left hand + OXR_SkeletonType_UE5Default_Left, + + // OpenXR Skeletal Right hand + OXR_SkeletonType_OpenXRDefault_Right, + // OpenXR Skeletal Left hand + OXR_SkeletonType_OpenXRDefault_Left, + + OXR_SkeletonType_Custom +}; + + + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRActionSkeletalData +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + EVRSkeletalHandIndex TargetHand; + + // A world scale override that will replace the engines current value and force into the tracked data if non zero + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + float WorldScaleOverride; + + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bAllowDeformingMesh; + + // If true then the bones will be mirrored from left/right, to allow you to swap a hand mesh or apply to a full body mesh + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bMirrorLeftRight; + + // List of aproximated curls for each finger + UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default) + TArray FingerCurls; + + UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default) + TArray SkeletalTransforms; + + // If true we will assume that the target skeleton does not have the metacarpal bones and we will not replicate them + // Only really used for the old UE4 skeleton + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bEnableUE4HandRepSavings; + + //UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default) + //TArray OldSkeletalTransforms; + + // The rotation required to rotate the finger bones back to X+ + // The animation node attempts to auto calculate it, if you have a non standard hand you may need to fill + // This in by yourself + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + FTransform AdditionTransform; + + UPROPERTY(NotReplicated, BlueprintReadOnly, Category = Default) + bool bHasValidData; + + FName LastHandGesture; + int32 LastHandGestureIndex; + + FBPOpenXRActionSkeletalData() + { + //bGetTransformsInParentSpace = false; + AdditionTransform = FTransform::Identity;// FTransform(FRotator(180.f, 0.f, -90.f), FVector::ZeroVector, FVector(1.f));//FTransform(FRotator(0.f, 90.f, 90.f), FVector::ZeroVector, FVector(1.f)); + WorldScaleOverride = 0.0f; + bAllowDeformingMesh = true; + bMirrorLeftRight = false; + bEnableUE4HandRepSavings = false; + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + bHasValidData = false; + LastHandGestureIndex = INDEX_NONE; + LastHandGesture = NAME_None; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalPair +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + EXRHandJointType OpenXRBone; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + FName BoneToTarget; + + FBoneReference ReferenceToConstruct; + FCompactPoseBoneIndex ParentReference; + FQuat RetargetRot; + + FBPOpenXRSkeletalPair() : + ParentReference(INDEX_NONE) + { + OpenXRBone = EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT; + BoneToTarget = NAME_None; + RetargetRot = FQuat::Identity; + } + + FBPOpenXRSkeletalPair(EXRHandJointType Bone, FString TargetBone) : + ParentReference(INDEX_NONE) + { + OpenXRBone = Bone; + BoneToTarget = FName(*TargetBone); + ReferenceToConstruct.BoneName = BoneToTarget; + RetargetRot = FQuat::Identity; + } + + FORCEINLINE bool operator==(const int32& Other) const + { + return ReferenceToConstruct.CachedCompactPoseIndex.GetInt() == Other; + //return ReferenceToConstruct.BoneIndex == Other; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalMappingData +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + TArray BonePairs; + + TArray ReverseBonePairMap; + + // Merge the transforms of bones that are missing from the OpenVR skeleton to the UE4 one. + // This should be always enabled for UE4 skeletons generally. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + bool bMergeMissingBonesUE4; + + // The hand data to get, if not using a custom bone mapping then this value will be auto filled + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + EVRSkeletalHandIndex TargetHand; + + FQuat AdjustmentQuat; + bool bInitialized; + + FName LastInitializedName; + EVROpenXRSkeletonType LastInitializedSkeleton; + + void ClearMapping() + { + bInitialized = false; + LastInitializedName = NAME_None; + AdjustmentQuat = FQuat::Identity; + LastInitializedSkeleton = EVROpenXRSkeletonType::OXR_SkeletonType_Custom; + + BonePairs.Empty(); + ReverseBonePairMap.Empty(); + } + + void ConstructReverseMapping() + { + int32 MaxElements = ((uint8)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT) + 1; + ReverseBonePairMap.Empty(MaxElements); + ReverseBonePairMap.AddUninitialized(MaxElements); + FMemory::Memset(ReverseBonePairMap.GetData(), 0, MaxElements * sizeof(int32)); + + + for (int i = 0; i < BonePairs.Num(); ++i) + { + // Just in case someone messed up the mapping file + if (i < MaxElements) + { + ReverseBonePairMap[(uint8)BonePairs[i].OpenXRBone] = i; + } + } + } + + void ConstructDefaultMappings(EVROpenXRSkeletonType SkeletonType, bool bSkipRootBone) + { + switch (SkeletonType) + { + + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Right: + { + bMergeMissingBonesUE4 = false; + SetDefaultOpenVRInputs(SkeletonType, bSkipRootBone); + }break; + case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right: + { + bMergeMissingBonesUE4 = true; + SetDefaultUE4Inputs(SkeletonType, bSkipRootBone); + }break; + case EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Right: + { + bMergeMissingBonesUE4 = false; + SetDefaultUE5Inputs(SkeletonType, bSkipRootBone); + }break; + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Right: + { + bMergeMissingBonesUE4 = false; + SetDefaultOpenXRInputs(SkeletonType, bSkipRootBone); + }break; + } + } + + void SetDefaultOpenVRInputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default OpenVR bones mapping + //if (!bSkipRootBone) + //{ + //BonePairs.Add(FBPOpenVRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_PALM_EXT, FString::Printf(TEXT("Root"), HandDelimiter))); + //} + + //if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("wrist_%s"), HandDelimiter))); + } + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("finger_thumb_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("finger_thumb_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, FString::Printf(TEXT("finger_thumb_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, FString::Printf(TEXT("finger_index_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("finger_index_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_index_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, FString::Printf(TEXT("finger_index_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, FString::Printf(TEXT("finger_middle_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_middle_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_middle_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, FString::Printf(TEXT("finger_middle_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, FString::Printf(TEXT("finger_ring_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("finger_ring_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_ring_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, FString::Printf(TEXT("finger_ring_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, FString::Printf(TEXT("finger_pinky_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_pinky_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_pinky_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT, FString::Printf(TEXT("finger_pinky_%s_end"), HandDelimiter))); + + // Aux bones share the final knuckles location / rotation + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_%s_aux"), HandDelimiter))); + } + + void SetDefaultUE4Inputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default ue4 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints + //if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter))); + } + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter))); + + } + + void SetDefaultUE5Inputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_UE5Default_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default ue5 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints + //if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter))); + } + + // There are inner and outer wrist elements to this, going to be anoying to map that to a single wrist index.... + //OXR_HAND_JOINT_WRIST_EXT = 1, + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter))); + + } + + void SetDefaultOpenXRInputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_OpenXRDefault_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default ue5 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints + //if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter))); + } + + // There are inner and outer wrist elements to this, going to be anoyying to map that to a single wrist index.... + //OXR_HAND_JOINT_WRIST_EXT = 1, + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter))); + + } + + FBPOpenXRSkeletalMappingData() + { + AdjustmentQuat = FQuat::Identity; + bInitialized = false; + bMergeMissingBonesUE4 = false; + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + LastInitializedName = NAME_None; + LastInitializedSkeleton = EVROpenXRSkeletonType::OXR_SkeletonType_Custom; + } +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h new file mode 100644 index 0000000..b27ff0f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h @@ -0,0 +1,377 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/Texture.h" +#include "Engine/EngineTypes.h" +#include "HeadMountedDisplayTypes.h" +//#include "Runtime/Launch/Resources/Version.h" + +#include "Animation/AnimInstanceProxy.h" +#include "OpenXRExpansionTypes.h" +#include "Engine/DataAsset.h" + +#include "OpenXRHandPoseComponent.generated.h" + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPXRSkeletalRepContainer +{ + GENERATED_BODY() +public: + + UPROPERTY(Transient, NotReplicated) + EVRSkeletalHandIndex TargetHand; + + UPROPERTY(Transient, NotReplicated) + bool bAllowDeformingMesh; + + // If true we will skip sending the 4 metacarpal bones that ue4 doesn't need, (STEAMVR skeletons need this disabled!) + // Only really used for the older UE4 skeleton + UPROPERTY(Transient, NotReplicated) + bool bEnableUE4HandRepSavings; + + UPROPERTY(Transient, NotReplicated) + TArray SkeletalTransforms; + + UPROPERTY(Transient, NotReplicated) + uint8 BoneCount; + + + FBPXRSkeletalRepContainer() + { + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left; + bAllowDeformingMesh = false; + bEnableUE4HandRepSavings = false; + BoneCount = 0; + } + + bool bHasValidData() + { + return SkeletalTransforms.Num() > 0; + } + + void CopyForReplication(FBPOpenXRActionSkeletalData& Other); + static void CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other); + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); +}; + +template<> +struct TStructOpsTypeTraits< FBPXRSkeletalRepContainer > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct OPENXREXPANSIONPLUGIN_API FOpenXRGestureFingerPosition +{ + GENERATED_BODY() +public: + + // The Finger index, not editable + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "VRGesture") + EXRHandJointType IndexType; + + // The locational value of this element 0.f - 1.f + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FVector Value; + + // The threshold within which this finger value will be detected as matching (1.0 would be always matching, IE: finger doesn't count) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture", meta = (ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "100.0")) + float Threshold; + + FOpenXRGestureFingerPosition(FVector TipLoc, EXRHandJointType Type) + { + IndexType = Type; + Value = TipLoc; + Threshold = 5.0f; + } + FOpenXRGestureFingerPosition() + { + IndexType = EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT; + Value = FVector(0.f); + Threshold = 5.0f; + } +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct OPENXREXPANSIONPLUGIN_API FOpenXRGesture +{ + GENERATED_BODY() +public: + + // Name of the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FName Name; + + // Samples in the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + TArray FingerValues; + + FOpenXRGesture() + { + InitPoseValues(); + Name = NAME_None; + } + + void InitPoseValues() + { + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT)); + } +}; + +/** +* Items Database DataAsset, here we can save all of our game items +*/ +UCLASS(BlueprintType, Category = "VRGestures") +class OPENXREXPANSIONPLUGIN_API UOpenXRGestureDatabase : public UDataAsset +{ + GENERATED_BODY() +public: + + // Gestures in this database + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TArray Gestures; + + UOpenXRGestureDatabase() + { + } +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureDetected, const FName &, GestureDetected, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureEnded, const FName &, GestureEnded, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENXREXPANSIONPLUGIN_API UOpenXRHandPoseComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer); + + // Says whether we should run gesture detection + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bDetectGestures; + + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void SetDetectGestures(bool bNewDetectGestures) + { + bDetectGestures = bNewDetectGestures; + } + + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FOpenXRGestureDetected OnNewGestureDetected; + + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FOpenXRGestureEnded OnGestureEnded; + + // Known sequences + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + UOpenXRGestureDatabase *GesturesDB; + + UFUNCTION(BlueprintCallable, Category = "VRGestures") + bool SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave = EVRSkeletalHandIndex::EActionHandIndex_Right); + + UFUNCTION(BlueprintCallable, Category = "VRGestures", meta = (DisplayName = "DetectCurrentPose")) + bool K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut); + + // This version throws events + bool DetectCurrentPose(FBPOpenXRActionSkeletalData& SkeletalAction); + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { +#if ENGINE_MAJOR_VERSION > 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 22) + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); +#else + // I like epics new authority check more than mine + const AActor* MyOwner = GetOwner(); + const APawn* MyPawn = Cast(MyOwner); + + return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner && MyOwner->GetLocalRole() == ENetRole::ROLE_Authority); +#endif + } + + // Using tick and not timers because skeletal components tick anyway, kind of a waste to make another tick by adding a timer over that + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + //virtual void OnUnregister() override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions") + bool bGetMockUpPoseForDebugging; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions") + TArray HandSkeletalActions; + + UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformLeft) + FBPXRSkeletalRepContainer LeftHandRep; + + UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformRight) + FBPXRSkeletalRepContainer RightHandRep; + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendSkeletalTransforms(const FBPXRSkeletalRepContainer& SkeletalInfo); + + bool bLerpingPositionLeft; + bool bLerpingPositionRight; + + struct FTransformLerpManager + { + bool bReplicatedOnce; + bool bLerping; + float UpdateCount; + float UpdateRate; + TArray OldTransforms; + TArray NewTransforms; + + FTransformLerpManager(); + void PreCopyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing); + void NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate, bool bExponentialSmoothing); + + FORCEINLINE void BlendBone(uint8 BoneToBlend, FBPOpenXRActionSkeletalData& ActionInfo, float& LerpVal, bool bExponentialSmoothing) + { + ActionInfo.SkeletalTransforms[BoneToBlend].Blend(OldTransforms[BoneToBlend], NewTransforms[BoneToBlend], LerpVal); + + if (bExponentialSmoothing) + { + // Saving base back out for exponential + OldTransforms[BoneToBlend] = ActionInfo.SkeletalTransforms[BoneToBlend]; + } + } + + void UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo, UOpenXRHandPoseComponent * ParentComp); + + }; + + FTransformLerpManager LeftHandRepManager; + FTransformLerpManager RightHandRepManager; + + UFUNCTION() + virtual void OnRep_SkeletalTransformLeft() + { + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == LeftHandRep.TargetHand) + { + if (bSmoothReplicatedSkeletalData) + { + LeftHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + + FBPXRSkeletalRepContainer::CopyReplicatedTo(LeftHandRep, HandSkeletalActions[i]); + + if (bSmoothReplicatedSkeletalData) + { + LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + + break; + } + } + } + + UFUNCTION() + virtual void OnRep_SkeletalTransformRight() + { + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == RightHandRep.TargetHand) + { + if (bSmoothReplicatedSkeletalData) + { + RightHandRepManager.PreCopyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + + FBPXRSkeletalRepContainer::CopyReplicatedTo(RightHandRep, HandSkeletalActions[i]); + + if (bSmoothReplicatedSkeletalData) + { + RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations, bUseExponentialSmoothing); + } + break; + } + } + } + + // If we should replicate the skeletal transform data + UPROPERTY(EditAnywhere, Category = SkeletalData) + bool bReplicateSkeletalData; + + // If true we will lerp between updates of the skeletal mesh transforms and smooth the result + UPROPERTY(EditAnywhere, Category = SkeletalData) + bool bSmoothReplicatedSkeletalData; + + // If true then we will use exponential smoothing with buffered correction + UPROPERTY(EditAnywhere, Category = "SkeletalData", meta = (editcondition = "bSmoothReplicatedSkeletalData")) + bool bUseExponentialSmoothing = false; + + // Timestep of smoothing translation + UPROPERTY(EditAnywhere, Category = "SkeletalData", meta = (editcondition = "bUseExponentialSmoothing")) + float InterpolationSpeed = 25.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SkeletalData) + float ReplicationRateForSkeletalAnimations; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float SkeletalNetUpdateCount; + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float SkeletalUpdateCount; +}; + +USTRUCT() +struct OPENXREXPANSIONPLUGIN_API FOpenXRAnimInstanceProxy : public FAnimInstanceProxy +{ +public: + GENERATED_BODY() + + FOpenXRAnimInstanceProxy() {} + FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance); + + /** Called before update so we can copy any data we need */ + virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override; + +public: + + EVRSkeletalHandIndex TargetHand; + TArray HandSkeletalActionData; + +}; + +UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType) +class OPENXREXPANSIONPLUGIN_API UOpenXRAnimInstance : public UAnimInstance +{ + GENERATED_BODY() + +public: + + UPROPERTY(transient) + UOpenXRHandPoseComponent * OwningPoseComp; + + FOpenXRAnimInstanceProxy AnimInstanceProxy; + + virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override + { + return new FOpenXRAnimInstanceProxy(this); + //return &AnimInstanceProxy; + } + + virtual void NativeBeginPlay() override; + + //virtual void NativeInitializeAnimation() override; + + UFUNCTION(BlueprintCallable, Category = "BoneMappings") + void InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData & SkeletalMappingData); + + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/README.md b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/README.md new file mode 100644 index 0000000..875fec0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/README.md @@ -0,0 +1,48 @@ +UE4 Forums Thread +https://forums.unrealengine.com/development-discussion/vr-ar-development/89050-vr-openvr-expansion-plugin + +Example Template Project +https://github.com/mordentral/VRExpPluginExample + +Website: +www.vreue4.com + +*** + +### Use Of This Plugin ### + +This Plugin is intended to add additional functionality to Open/SteamVR/(All VR now) in UE4. + +### Plugin Website ### +[VREUE4.com](https://vreue4.com) + +### How do I install it? ### + +https://vreue4.com/documentation?section=installation + +**Guides for migrating between different engine versions of the plugin:** + +View the patch notes at www.vreue4.com for migration guides as well. + +**Option 1:** + +Go to www.vreue4.com and downloaded the pre-built binary version for the engine version you are using (not updated with every daily change, only weekly or with large patches). + +Install it into your ProjectName/Plugins Directory (Engine level hasn't worked since 4.25 or so when it stopped letting me reference other plugins when compiling for that). + +**Option 2 (More up to date - preferred if possible):** + +* Clone Or Download Zip and extract this repository to a folder named "VRExpansionPlugin" in your "ProjectName/Plugins" directory, create this directory if it is missing. + +* Add the VRExpansionPlugin to your projects PublicDependencyModuleNames in the projects build.cs if you have c++ code included. + +* IF you do not have c++ code, use the Add New button in the editor and add a blank c++ class to your project. + +* Open up the generated project .SLN file and build the project from the build menu. + +You need to have visual studio installed and follow the UE4 setup guide for it: https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/ + +### How do I use it? ### +### How do I VR? ### + +The template project contains use examples of most of the features of the plugin as well as locomotion modes, interaction methods, and basic multiplayer. diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[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 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png new file mode 100644 index 0000000..23fec31 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8f329acc9d0c2815d28349d5a4144ebd337f7e2a93819cf1a7c3cc9b06ecea +size 16085 diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp new file mode 100644 index 0000000..cc31a36 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp @@ -0,0 +1,849 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "HandSocketComponentDetails.h" +#include "HandSocketVisualizer.h" +//#include "PropertyEditing.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +#include "PropertyHandle.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "DetailCategoryBuilder.h" +#include "IDetailsView.h" + +#include "Developer/AssetTools/Public/IAssetTools.h" +#include "Developer/AssetTools/Public/AssetToolsModule.h" +#include "Editor/ContentBrowser/Public/IContentBrowserSingleton.h" +#include "Editor/ContentBrowser/Public/ContentBrowserModule.h" +#include "AnimationUtils.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "UObject/SavePackage.h" +#include "Misc/MessageDialog.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Editor.h" +#include "EditorStyleSet.h" +#include "Styling/CoreStyle.h" + +#include "Animation/AnimData/AnimDataModel.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" + +#define LOCTEXT_NAMESPACE "HandSocketComponentDetails" + +FText SCreateHandAnimationDlg::LastUsedAssetPath; + +static bool PromptUserForAssetPath(FString& AssetPath, FString& AssetName) +{ + TSharedRef NewAnimDlg = SNew(SCreateHandAnimationDlg); + if (NewAnimDlg->ShowModal() != EAppReturnType::Cancel) + { + AssetPath = NewAnimDlg->GetFullAssetPath(); + AssetName = NewAnimDlg->GetAssetName(); + return true; + } + + return false; +} + +TWeakObjectPtr FHandSocketComponentDetails::SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName) +{ + + TWeakObjectPtr FinalAnimation; + + // Replace when this moves to custom display + if (!HandSocketComponent.IsValid()) + return FinalAnimation; + + /*if (!HandSocketComponent->HandVisualizerComponent)// || !HandSocketComponent->HandVisualizerComponent->SkeletalMesh || !HandSocketComponent->HandVisualizerComponent->SkeletalMesh->Skeleton) + { + return false; + }*/ + + if (!HandSocketComponent->HandTargetAnimation && (!HandSocketComponent->VisualizationMesh || !HandSocketComponent->VisualizationMesh->GetSkeleton())) + { + return FinalAnimation; + } + + // create the asset + FText InvalidPathReason; + bool const bValidPackageName = FPackageName::IsValidLongPackageName(InAssetPath, false, &InvalidPathReason); + if (bValidPackageName == false) + { + UE_LOG(LogAnimation, Log, TEXT("%s is an invalid asset path, prompting user for new asset path. Reason: %s"), *InAssetPath, *InvalidPathReason.ToString()); + } + + FString ValidatedAssetPath = InAssetPath; + FString ValidatedAssetName = InAssetName; + + UObject* Parent = bValidPackageName ? CreatePackage(*ValidatedAssetPath) : nullptr; + if (Parent == nullptr) + { + // bad or no path passed in, do the popup + if (PromptUserForAssetPath(ValidatedAssetPath, ValidatedAssetName) == false) + { + return FinalAnimation; + } + + Parent = CreatePackage(*ValidatedAssetPath); + } + + UObject* const Object = LoadObject(Parent, *ValidatedAssetName, nullptr, LOAD_Quiet, nullptr); + // if object with same name exists, warn user + if (Object) + { + EAppReturnType::Type ReturnValue = FMessageDialog::Open(EAppMsgType::YesNo, NSLOCTEXT("UnrealEd", "Error_AssetExist", "Asset with same name exists. Do you wish to overwrite it?")); + if (ReturnValue == EAppReturnType::No) + { + return FinalAnimation; // failed + } + } + + UAnimSequence* BaseAnimation = HandSocketComponent->HandTargetAnimation; + TArray LocalPoses; + + if (!BaseAnimation) + { + LocalPoses = HandSocketComponent->VisualizationMesh->GetSkeleton()->GetRefLocalPoses(); + } + + // If not, create new one now. + UAnimSequence* const NewSeq = NewObject(Parent, *ValidatedAssetName, RF_Public | RF_Standalone); + if (NewSeq) + { + // set skeleton + if (BaseAnimation) + { + NewSeq->SetSkeleton(BaseAnimation->GetSkeleton()); + } + else + { + NewSeq->SetSkeleton(HandSocketComponent->VisualizationMesh->GetSkeleton()); + } + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(NewSeq); + //StartRecord(Component, NewSeq); + + //return true; + UAnimSequence* AnimationObject = NewSeq; + + IAnimationDataController& AnimController = AnimationObject->GetController(); + { + IAnimationDataController::FScopedBracket ScopedBracket(AnimController, LOCTEXT("SaveAnimationAsset_VRE", "Creating Animation Sequence based on hand pose")); + AnimationObject->ResetAnimation(); + if (BaseAnimation) + { + AnimationObject->BoneCompressionSettings = BaseAnimation->BoneCompressionSettings; + } + else + { + AnimationObject->BoneCompressionSettings = FAnimationUtils::GetDefaultAnimationBoneCompressionSettings(); + } + + AnimController.InitializeModel(); + AnimController.RemoveAllBoneTracks(false); + + + //checkf(MovieScene, TEXT("No Movie Scene found for SequencerDataModel")); + //AnimController.SetPlayLength(4.f); + AnimController.SetNumberOfFrames(FFrameNumber(1), false); + + // Set frame rate to 1 to 1 as we don't animate + AnimController.SetFrameRate(FFrameRate(1, 1)); + + TArray TrackNames; + const IAnimationDataModel* BaseDataModel = BaseAnimation ? BaseAnimation->GetController().GetModel() : nullptr; + + if (BaseAnimation) + { + if (BaseDataModel) + { + BaseDataModel->GetBoneTrackNames(TrackNames); + for (FName TrackName : TrackNames) + { + AnimController.AddBoneCurve(TrackName); + } + } + else + { + return FinalAnimation; + } + } + else + { + int numBones = HandSocketComponent->VisualizationMesh->GetRefSkeleton().GetNum(); + for (int i = 0; i < LocalPoses.Num() && i < numBones; ++i) + { + AnimController.AddBoneCurve(HandSocketComponent->VisualizationMesh->GetRefSkeleton().GetBoneName(i)); + //AnimController.AddBoneTrack(HandSocketComponent->VisualizationMesh->GetRefSkeleton().GetBoneName(i)); + } + } + + if (BaseAnimation) + { + AnimationObject->RetargetSource = BaseAnimation->RetargetSource; + } + else + { + AnimationObject->RetargetSource = HandSocketComponent->VisualizationMesh ? HandSocketComponent->VisualizationMesh->GetSkeleton()->GetRetargetSourceForMesh(HandSocketComponent->VisualizationMesh) : NAME_None; + } + + const IAnimationDataModel* DataModel = AnimController.GetModel(); + + /// SAVE POSE + if (BaseAnimation && DataModel && BaseDataModel) + { + for (int32 TrackIndex = 0; TrackIndex < /*DataModel->GetBoneAnimationTracks().Num()*/BaseDataModel->GetNumBoneTracks(); ++TrackIndex) + { + FName TrackName = TrackIndex < TrackNames.Num() ? TrackNames[TrackIndex] : NAME_None; + if (!BaseDataModel->IsValidBoneTrackName(TrackName)) + { + continue; + } + + FTransform FinalTrans = BaseDataModel->GetBoneTrackTransform(TrackName, 0); + //FTransform FinalTrans(Rot, Loc, Scale); + + FQuat DeltaQuat = FQuat::Identity; + for (FBPVRHandPoseBonePair& HandPair : HandSocketComponent->CustomPoseDeltas) + { + if (HandPair.BoneName == TrackName) + { + DeltaQuat = HandPair.DeltaPose; + break; + } + } + + FinalTrans.ConcatenateRotation(DeltaQuat); + FinalTrans.NormalizeRotation(); + + //FRawAnimSequenceTrack& RawNewTrack = DataModel->GetBoneTrackByIndex(TrackIndex).InternalTrackData; + AnimController.SetBoneTrackKeys(TrackName, { FinalTrans.GetTranslation() }, { FinalTrans.GetRotation() }, { FinalTrans.GetScale3D() }); + } + } + else if(DataModel) + { + USkeletalMesh* SkeletalMesh = HandSocketComponent->VisualizationMesh; + FReferenceSkeleton RefSkeleton = SkeletalMesh->GetRefSkeleton(); + USkeleton* AnimSkeleton = SkeletalMesh->GetSkeleton(); + + for (int32 TrackIndex = 0; TrackIndex < RefSkeleton.GetNum(); ++TrackIndex) + { + + FName TrackName = RefSkeleton.GetBoneName(TrackIndex); + if (!DataModel->IsValidBoneTrackName(TrackName)) + { + continue; + } + + int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(TrackName); + + // verify if this bone exists in skeleton + //int32 BoneTreeIndex = DataModel->GetBoneTrackByIndex(TrackIndex).BoneTreeIndex; + + if (BoneTreeIndex != INDEX_NONE) + { + + int32 BoneIndex = BoneTreeIndex;//AnimSkeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkeletalMesh, BoneTreeIndex); + //int32 ParentIndex = SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex); + FTransform LocalTransform = LocalPoses[BoneIndex]; + + + FName BoneName = AnimSkeleton->GetReferenceSkeleton().GetBoneName(BoneIndex); + + FQuat DeltaQuat = FQuat::Identity; + for (FBPVRHandPoseBonePair& HandPair : HandSocketComponent->CustomPoseDeltas) + { + if (HandPair.BoneName == BoneName) + { + DeltaQuat = HandPair.DeltaPose; + } + } + + LocalTransform.ConcatenateRotation(DeltaQuat); + LocalTransform.NormalizeRotation(); + + AnimController.SetBoneTrackKeys(BoneName, { LocalTransform.GetTranslation() }, { LocalTransform.GetRotation() }, { LocalTransform.GetScale3D() }); + } + } + } + + AnimController.NotifyPopulated(); + } + /// END SAVE POSE + /// + /// + /// + + // init notifies + AnimationObject->InitializeNotifyTrack(); + //#TODO: 5.1, need to figure out what they replaced this with + //PRAGMA_DISABLE_DEPRECATION_WARNINGS + //AnimationObject->PostProcessSequence(); + //PRAGMA_ENABLE_DEPRECATION_WARNINGS + AnimationObject->MarkPackageDirty(); + + //if (bAutoSaveAsset) + { + UPackage* const Package = AnimationObject->GetOutermost(); + FString const PackageName = Package->GetName(); + FString const PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); + + double StartTime = FPlatformTime::Seconds(); + + FSavePackageArgs PackageArguments; + PackageArguments.SaveFlags = RF_Standalone; + PackageArguments.SaveFlags = SAVE_NoError; + UPackage::SavePackage(Package, NULL, *PackageFileName, PackageArguments); + //UPackage::SavePackage(Package, NULL, RF_Standalone, *PackageFileName, GError, nullptr, false, true, SAVE_NoError); + + double ElapsedTime = FPlatformTime::Seconds() - StartTime; + UE_LOG(LogAnimation, Log, TEXT("Animation Recorder saved %s in %0.2f seconds"), *PackageName, ElapsedTime); + } + + FinalAnimation = AnimationObject; + return FinalAnimation; + } + + return FinalAnimation; +} +TSharedRef< IDetailCustomization > FHandSocketComponentDetails::MakeInstance() +{ + return MakeShareable(new FHandSocketComponentDetails); +} + +void FHandSocketComponentDetails::OnHandRelativeUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + } + } + + FComponentVisualizer::NotifyPropertyModified(HandSocketComponent.Get(), FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); +} + +void FHandSocketComponentDetails::OnLeftDominantUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + // Default to always flipping this + //if (HandSocketComponent->bFlipForLeftHand) + { + FTransform relTrans = HandSocketComponent->GetRelativeTransform(); + FTransform HandPlacement = HandSocketComponent->GetHandRelativePlacement(); + + if (HandSocketComponent->bDecoupleMeshPlacement) + { + relTrans = FTransform::Identity; + } + + FTransform ReturnTrans = (HandPlacement * relTrans); + + HandSocketComponent->MirrorHandTransform(ReturnTrans, relTrans); + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + ReturnTrans = ReturnTrans.GetRelativeTransform(relTrans); + HandSocketComponent->HandRelativePlacement = ReturnTrans; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + + FComponentVisualizer::NotifyPropertyModified(HandSocketComponent.Get(), FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } +} + +void FHandSocketComponentDetails::OnLockedStateUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + if (HandSocketComponent->bDecoupleMeshPlacement) + { + //FTransform RelTrans = HandSocketComponent->GetRelativeTransform(); + //FTransform WorldTrans = HandSocketComponent->GetComponentTransform(); + //if (USceneComponent* ParentComp = HandSocketComponent->GetAttachParent()) + { + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + HandSocketComponent->HandRelativePlacement = HandSocketComponent->HandRelativePlacement * HandSocketComponent->GetRelativeTransform();// HandSocketComponent->GetComponentTransform(); + HandSocketComponent->bDecoupled = true; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + RefHand->bDecoupled = true; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + } + } + else + { + //if (USceneComponent* ParentComp = HandSocketComponent->GetAttachParent()) + { + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + HandSocketComponent->HandRelativePlacement = HandSocketComponent->HandRelativePlacement.GetRelativeTransform(HandSocketComponent->GetRelativeTransform()); + HandSocketComponent->bDecoupled = false; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + RefHand->bDecoupled = false; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + } + } + + TArray PropertiesToModify; + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bDecoupled))); + FComponentVisualizer::NotifyPropertiesModified(HandSocketComponent.Get(), PropertiesToModify); +} + +void FHandSocketComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Hide the SplineCurves property + //TSharedPtr HandPlacementProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)); + //HandPlacementProperty->MarkHiddenByCustomization(); + + + TArray> ObjectsBeingCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + + if (ObjectsBeingCustomized.Num() == 1) + { + UHandSocketComponent* CurrentHandSocket = Cast(ObjectsBeingCustomized[0]); + if (CurrentHandSocket != NULL) + { + if (HandSocketComponent != CurrentHandSocket) + { + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(CurrentHandSocket->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + //HandVisualizer->OldHandSocketComp = CurrentHandSocket; + } + + HandSocketComponent = CurrentHandSocket; + } + } + } + + /*const TArray< TWeakObjectPtr >& SelectedObjects = DetailBuilder.GetSelectedObjects(); + for (int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex) + { + const TWeakObjectPtr& CurrentObject = SelectedObjects[ObjectIndex]; + if (CurrentObject.IsValid()) + { + UHandSocketComponent* CurrentHandSocket = Cast(CurrentObject.Get()); + if (CurrentHandSocket != NULL) + { + if (HandSocketComponent != CurrentHandSocket) + { + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(CurrentHandSocket->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + //HandVisualizer->OldHandSocketComp = CurrentHandSocket; + } + + HandSocketComponent = CurrentHandSocket; + } + break; + } + } + }*/ + + DetailBuilder.HideCategory(FName("ComponentTick")); + DetailBuilder.HideCategory(FName("GameplayTags")); + DetailBuilder.HideCategory(FName("VRGripInterface")); + DetailBuilder.HideCategory(FName("VRGripInterface|Replication")); + DetailBuilder.HideCategory(FName("Tags")); + DetailBuilder.HideCategory(FName("AssetUserData")); + DetailBuilder.HideCategory(FName("Events")); + DetailBuilder.HideCategory(FName("Activation")); + DetailBuilder.HideCategory(FName("Cooking")); + DetailBuilder.HideCategory(FName("ComponentReplication")); + + TSharedPtr LockedLocationProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bDecoupleMeshPlacement)); + TSharedPtr HandRelativePlacementProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)); + TSharedPtr LeftHandDominateProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bLeftHandDominant)); + + FSimpleDelegate OnHandRelativeChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnHandRelativeUpdated, &DetailBuilder); + HandRelativePlacementProperty->SetOnPropertyValueChanged(OnHandRelativeChangedDelegate); + + FSimpleDelegate OnLockedStateChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnLockedStateUpdated, &DetailBuilder); + LockedLocationProperty->SetOnPropertyValueChanged(OnLockedStateChangedDelegate); + + FSimpleDelegate OnLeftDominateChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnLeftDominantUpdated, &DetailBuilder); + LeftHandDominateProperty->SetOnPropertyValueChanged(OnLeftDominateChangedDelegate); + + TSharedPtr ShowVisualizationProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bShowVisualizationMesh)); + + FSimpleDelegate OnShowVisChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnUpdateShowMesh, &DetailBuilder); + ShowVisualizationProperty->SetOnPropertyValueChanged(OnShowVisChangedDelegate); + + DetailBuilder.EditCategory("Hand Animation") + .AddCustomRow(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save Current Pose")) + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save Current Pose")) + ] + .ValueContent() + .MaxDesiredWidth(125.f) + .MinDesiredWidth(125.f) + [ + SNew(SButton) + .ContentPadding(2) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHandSocketComponentDetails::OnUpdateSavePose) + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save")) + ] + ]; +} + +void FHandSocketComponentDetails::OnUpdateShowMesh(IDetailLayoutBuilder* LayoutBuilder) +{ + if (!HandSocketComponent.IsValid()) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + } +} + +FReply FHandSocketComponentDetails::OnUpdateSavePose() +{ + if (HandSocketComponent.IsValid() && HandSocketComponent->CustomPoseDeltas.Num() > 0) + { + if (HandSocketComponent->HandTargetAnimation || HandSocketComponent->VisualizationMesh) + { + // Save Animation Pose here + FString AssetPath; + FString AssetName; + PromptUserForAssetPath(AssetPath, AssetName); + TWeakObjectPtr NewAnim = SaveAnimationAsset(AssetPath, AssetName); + + // Finally remove the deltas + if (NewAnim.IsValid()) + { + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + HandSocketComponent->HandTargetAnimation = NewAnim.Get(); + HandSocketComponent->CustomPoseDeltas.Empty(); + HandSocketComponent->bUseCustomPoseDeltas = false; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandTargetAnimation = NewAnim.Get(); + RefHand->CustomPoseDeltas.Empty(); + RefHand->bUseCustomPoseDeltas = false; + /*TArray PropertiesToModify; + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation))); + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bUseCustomPoseDeltas))); + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + FComponentVisualizer::NotifyPropertiesModified(RefHand, PropertiesToModify);*/ + } + } + + // Modify all of the properties at once + TArray PropertiesToModify; + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation))); + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bUseCustomPoseDeltas))); + PropertiesToModify.Add(FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + FComponentVisualizer::NotifyPropertiesModified(HandSocketComponent.Get(), PropertiesToModify); + } + } + } + + return FReply::Handled(); +} + +void SCreateHandAnimationDlg::Construct(const FArguments& InArgs) +{ + AssetPath = FText::FromString(FPackageName::GetLongPackagePath(InArgs._DefaultAssetPath.ToString())); + AssetName = FText::FromString(FPackageName::GetLongPackageAssetName(InArgs._DefaultAssetPath.ToString())); + + if (AssetPath.IsEmpty()) + { + AssetPath = LastUsedAssetPath; + // still empty? + if (AssetPath.IsEmpty()) + { + AssetPath = FText::FromString(TEXT("/Game")); + } + } + else + { + LastUsedAssetPath = AssetPath; + } + + if (AssetName.IsEmpty()) + { + // find default name for them + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FString OutPackageName, OutAssetName; + FString PackageName = AssetPath.ToString() + TEXT("/NewAnimation"); + + AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), OutPackageName, OutAssetName); + AssetName = FText::FromString(OutAssetName); + } + + FPathPickerConfig PathPickerConfig; + PathPickerConfig.DefaultPath = AssetPath.ToString(); + PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SCreateHandAnimationDlg::OnPathChange); + PathPickerConfig.bAddDefaultPath = true; + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + SWindow::Construct(SWindow::FArguments() + .Title(LOCTEXT("SCreateHandAnimationDlg_Title", "Create New Animation Object")) + .SupportsMinimize(false) + .SupportsMaximize(false) + //.SizingRule( ESizingRule::Autosized ) + .ClientSize(FVector2D(450, 450)) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() // Add user input block + .Padding(2) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectPath", "Select Path to create animation")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 14)) + ] + + + SVerticalBox::Slot() + .FillHeight(1) + .Padding(3) + [ + ContentBrowserModule.Get().CreatePathPicker(PathPickerConfig) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SSeparator) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(3) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 0, 10, 0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("AnimationName", "Animation Name")) + ] + + + SHorizontalBox::Slot() + [ + SNew(SEditableTextBox) + .Text(AssetName) + .OnTextCommitted(this, &SCreateHandAnimationDlg::OnNameChange) + .MinDesiredWidth(250) + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(5) + [ + SNew(SUniformGridPanel) + .SlotPadding(FAppStyle::GetMargin("StandardDialog.SlotPadding")) + .MinDesiredSlotWidth(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) + .MinDesiredSlotHeight(FAppStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) + .Text(LOCTEXT("OK", "OK")) + .OnClicked(this, &SCreateHandAnimationDlg::OnButtonClick, EAppReturnType::Ok) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FAppStyle::GetMargin("StandardDialog.ContentPadding")) + .Text(LOCTEXT("Cancel", "Cancel")) + .OnClicked(this, &SCreateHandAnimationDlg::OnButtonClick, EAppReturnType::Cancel) + ] + ] + ]); +} + +void SCreateHandAnimationDlg::OnNameChange(const FText& NewName, ETextCommit::Type CommitInfo) +{ + AssetName = NewName; +} + +void SCreateHandAnimationDlg::OnPathChange(const FString& NewPath) +{ + AssetPath = FText::FromString(NewPath); + LastUsedAssetPath = AssetPath; +} + +FReply SCreateHandAnimationDlg::OnButtonClick(EAppReturnType::Type ButtonID) +{ + UserResponse = ButtonID; + + if (ButtonID != EAppReturnType::Cancel) + { + if (!ValidatePackage()) + { + // reject the request + return FReply::Handled(); + } + } + + RequestDestroyWindow(); + + return FReply::Handled(); +} + +/** Ensures supplied package name information is valid */ +bool SCreateHandAnimationDlg::ValidatePackage() +{ + FText Reason; + FString FullPath = GetFullAssetPath(); + + if (!FPackageName::IsValidLongPackageName(FullPath, false, &Reason) + || !FName(*AssetName.ToString()).IsValidObjectName(Reason)) + { + FMessageDialog::Open(EAppMsgType::Ok, Reason); + return false; + } + + return true; +} + +EAppReturnType::Type SCreateHandAnimationDlg::ShowModal() +{ + GEditor->EditorAddModalWindow(SharedThis(this)); + return UserResponse; +} + +FString SCreateHandAnimationDlg::GetAssetPath() +{ + return AssetPath.ToString(); +} + +FString SCreateHandAnimationDlg::GetAssetName() +{ + return AssetName.ToString(); +} + +FString SCreateHandAnimationDlg::GetFullAssetPath() +{ + return AssetPath.ToString() + "/" + AssetName.ToString(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp new file mode 100644 index 0000000..e7da798 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp @@ -0,0 +1,465 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "HandSocketVisualizer.h" +#include "CanvasItem.h" +#include "CanvasTypes.h" +#include "SceneManagement.h" +//#include "UObject/Field.h" +#include "VRBPDatatypes.h" +#include "ScopedTransaction.h" +#include "Modules/ModuleManager.h" +#include "EditorViewportClient.h" +#include "Components/PoseableMeshComponent.h" +#include "Misc/PackageName.h" +//#include "Persona.h" + +IMPLEMENT_HIT_PROXY(HHandSocketVisProxy, HComponentVisProxy); +#define LOCTEXT_NAMESPACE "HandSocketVisualizer" + +bool FHandSocketVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bool bEditing = false; + if (VisProxy && VisProxy->Component.IsValid()) + { + bEditing = true; + if (VisProxy->IsA(HHandSocketVisProxy::StaticGetType())) + { + + if( const UHandSocketComponent * HandComp = UpdateSelectedHandComponent(VisProxy)) + { + HHandSocketVisProxy* Proxy = (HHandSocketVisProxy*)VisProxy; + if (Proxy) + { + CurrentlySelectedBone = Proxy->TargetBoneName; + CurrentlySelectedBoneIdx = Proxy->BoneIdx; + TargetViewport = InViewportClient->Viewport; + } + } + } + } + + return bEditing; +} + + +bool FHandSocketVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const +{ + if (TargetViewport == nullptr || TargetViewport != ViewportClient->Viewport) + { + return false; + } + + if (HandPropertyPath.IsValid() && CurrentlySelectedBone != NAME_None/* && CurrentlySelectedBone != "HandSocket"*/) + { + if (CurrentlySelectedBone == "HandSocket") + { + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (CurrentlyEditingComponent) + { + if (CurrentlyEditingComponent->bMirrorVisualizationMesh) + { + FTransform NewTrans = CurrentlyEditingComponent->GetRelativeTransform(); + NewTrans.Mirror(CurrentlyEditingComponent->GetAsEAxis(CurrentlyEditingComponent->MirrorAxis), CurrentlyEditingComponent->GetAsEAxis(CurrentlyEditingComponent->FlipAxis)); + + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + NewTrans = NewTrans * ParentComp->GetComponentTransform(); + } + + OutMatrix = FRotationMatrix::Make(NewTrans.GetRotation()); + } + } + + return false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + + FTransform newTrans = FTransform::Identity; + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + newTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + newTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + OutMatrix = FRotationMatrix::Make(newTrans.GetRotation()); + } + } + else + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + FTransform newTrans = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx); + OutMatrix = FRotationMatrix::Make(newTrans.GetRotation()); + } + } + + return true; + } + + return false; +} + +bool FHandSocketVisualizer::IsVisualizingArchetype() const +{ + return (HandPropertyPath.IsValid() && HandPropertyPath.GetParentOwningActor() && FActorEditorUtils::IsAPreviewOrInactiveActor(HandPropertyPath.GetParentOwningActor())); +} + +void FHandSocketVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + if (TargetViewport == nullptr || TargetViewport != Viewport) + { + return; + } + + if (const UHandSocketComponent* HandComp = Cast(Component)) + { + if (CurrentlySelectedBone != NAME_None) + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + if (!CurrentlyEditingComponent->HandVisualizerComponent) + { + return; + } + + int32 XL; + int32 YL; + const FIntRect CanvasRect = Canvas->GetViewRect(); + + FPlane location = View->Project(CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx).GetLocation()); + StringSize(GEngine->GetLargeFont(), XL, YL, *CurrentlySelectedBone.ToString()); + //const float DrawPositionX = location.X - XL; + //const float DrawPositionY = location.Y - YL; + const float DrawPositionX = FMath::FloorToFloat(CanvasRect.Min.X + (CanvasRect.Width() - XL) * 0.5f); + const float DrawPositionY = CanvasRect.Min.Y + 50.0f; + Canvas->DrawShadowedString(DrawPositionX, DrawPositionY, *CurrentlySelectedBone.ToString(), GEngine->GetLargeFont(), FLinearColor::Yellow); + } + } + } +} + +void FHandSocketVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + //UWorld* World = Component->GetWorld(); + //return World && (World->WorldType == EWorldType::EditorPreview || World->WorldType == EWorldType::Inactive); + + //cast the component into the expected component type + if (const UHandSocketComponent* HandComponent = Cast(Component)) + { + if (!HandComponent->HandVisualizerComponent) + return; + + //This is an editor only uproperty of our targeting component, that way we can change the colors if we can't see them against the background + const FLinearColor SelectedColor = FLinearColor::Yellow;//TargetingComponent->EditorSelectedColor; + const FLinearColor UnselectedColor = FLinearColor::White;//TargetingComponent->EditorUnselectedColor; + const FVector Location = HandComponent->HandVisualizerComponent->GetComponentLocation(); + float BoneScale = 1.0f - ((View->ViewLocation - Location).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.2f, 1.0f); + HHandSocketVisProxy* newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = "Visualizer"; + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(Location, CurrentlySelectedBone == newHitProxy->TargetBoneName ? SelectedColor : FLinearColor::Red, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + + newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = "HandSocket"; + BoneScale = 1.0f - ((View->ViewLocation - HandComponent->GetComponentLocation()).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.2f, 1.0f); + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(HandComponent->GetComponentLocation(), FLinearColor::Green, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + + if (HandComponent->bUseCustomPoseDeltas) + { + TArray BoneTransforms = HandComponent->HandVisualizerComponent->GetBoneSpaceTransforms(); + FTransform ParentTrans = HandComponent->HandVisualizerComponent->GetComponentTransform(); + // We skip root bone, moving the visualizer itself handles that + for (int i = 1; i < HandComponent->HandVisualizerComponent->GetNumBones(); i++) + { + + FName BoneName = HandComponent->HandVisualizerComponent->GetBoneName(i); + + if (HandComponent->bFilterBonesByPostfix) + { + if (BoneName.ToString().Right(2) != HandComponent->FilterPostfix) + { + // Skip visualizing this bone its the incorrect side + continue; + } + } + + if (HandComponent->BonesToSkip.Contains(BoneName)) + { + // Skip visualizing this bone as its in the ignore array + continue; + } + + FTransform BoneTransform = HandComponent->HandVisualizerComponent->GetBoneTransform(i); + FVector BoneLoc = BoneTransform.GetLocation(); + BoneScale = 1.0f - ((View->ViewLocation - BoneLoc).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.1f, 0.9f); + newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = BoneName; + newHitProxy->BoneIdx = i; + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(BoneLoc, CurrentlySelectedBone == newHitProxy->TargetBoneName ? SelectedColor : UnselectedColor, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + } + } + + if (HandComponent->bShowRangeVisualization) + { + float RangeVisualization = HandComponent->OverrideDistance; + + if (RangeVisualization <= 0.0f) + { + if (USceneComponent* Parent = Cast(HandComponent->GetAttachParent())) + { + FStructProperty* ObjectProperty = CastField(Parent->GetClass()->FindPropertyByName("VRGripInterfaceSettings")); + + AActor* ParentsActor = nullptr; + if (!ObjectProperty) + { + ParentsActor = Parent->GetOwner(); + if (ParentsActor) + { + ObjectProperty = CastField(Parent->GetOwner()->GetClass()->FindPropertyByName("VRGripInterfaceSettings")); + } + } + + if (ObjectProperty) + { + UObject* Target = ParentsActor; + + if (Target == nullptr) + { + Target = Parent; + } + + if (const FBPInterfaceProperties* Curve = ObjectProperty->ContainerPtrToValuePtr(Target)) + { + if (HandComponent->SlotPrefix == "VRGripS") + { + RangeVisualization = Curve->SecondarySlotRange; + } + else + { + RangeVisualization = Curve->PrimarySlotRange; + } + } + } + } + } + + // Scale into our parents space as that is actually what the range is based on + FBox BoxToDraw = FBox::BuildAABB(FVector::ZeroVector, FVector(RangeVisualization) * HandComponent->GetAttachParent()->GetComponentScale()); + BoxToDraw.Min += HandComponent->GetComponentLocation(); + BoxToDraw.Max += HandComponent->GetComponentLocation(); + + DrawWireBox(PDI, BoxToDraw, FColor::Green, 0.0f); + } + } +} + +bool FHandSocketVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const +{ + if (TargetViewport == nullptr || TargetViewport != ViewportClient->Viewport) + { + return false; + } + + if (HandPropertyPath.IsValid() && CurrentlySelectedBone != NAME_None && CurrentlySelectedBone != "HandSocket") + { + if (CurrentlySelectedBone == "HandSocket") + { + return false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + FTransform newTrans = FTransform::Identity; + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + newTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + newTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + OutLocation = newTrans.GetLocation(); + } + } + else + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + OutLocation = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx).GetLocation(); + } + } + + return true; + } + + return false; +} + +bool FHandSocketVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) +{ + + if (TargetViewport == nullptr || TargetViewport != Viewport) + { + return false; + } + + bool bHandled = false; + + if (HandPropertyPath.IsValid()) + { + if (CurrentlySelectedBone == "HandSocket" || CurrentlySelectedBone == NAME_None) + { + bHandled = false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + const FScopedTransaction Transaction(LOCTEXT("ChangingComp", "ChangingComp")); + + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (!CurrentlyEditingComponent) + { + return false; + } + + CurrentlyEditingComponent->Modify(); + if (AActor* Owner = CurrentlyEditingComponent->GetOwner()) + { + Owner->Modify(); + } + bool bLevelEdit = ViewportClient->IsLevelEditorClient(); + + FTransform CurrentTrans = FTransform::Identity; + + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + CurrentTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + CurrentTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + if (!DeltaTranslate.IsNearlyZero()) + { + CurrentTrans.AddToTranslation(DeltaTranslate); + } + + if (!DeltaRotate.IsNearlyZero()) + { + CurrentTrans.SetRotation(DeltaRotate.Quaternion() * CurrentTrans.GetRotation()); + } + + if (!DeltaScale.IsNearlyZero()) + { + CurrentTrans.MultiplyScale3D(DeltaScale); + } + + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + CurrentlyEditingComponent->HandRelativePlacement = CurrentTrans.GetRelativeTransform(ParentComp->GetComponentTransform()); + } + } + else + { + CurrentlyEditingComponent->HandRelativePlacement = CurrentTrans.GetRelativeTransform(CurrentlyEditingComponent->GetComponentTransform()); + } + + NotifyPropertyModified(CurrentlyEditingComponent, FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + //GEditor->RedrawLevelEditingViewports(true); + bHandled = true; + + } + else + { + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (!CurrentlyEditingComponent || !CurrentlyEditingComponent->HandVisualizerComponent) + { + return false; + } + + const FScopedTransaction Transaction(LOCTEXT("ChangingComp", "ChangingComp")); + + CurrentlyEditingComponent->Modify(); + if (AActor* Owner = CurrentlyEditingComponent->GetOwner()) + { + Owner->Modify(); + } + bool bLevelEdit = ViewportClient->IsLevelEditorClient(); + + FTransform BoneTrans = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx); + FTransform NewTrans = BoneTrans; + NewTrans.SetRotation(DeltaRotate.Quaternion() * NewTrans.GetRotation()); + + FQuat DeltaRotateMod = NewTrans.GetRelativeTransform(BoneTrans).GetRotation(); + bool bFoundBone = false; + for (FBPVRHandPoseBonePair& BonePair : CurrentlyEditingComponent->CustomPoseDeltas) + { + if (BonePair.BoneName == CurrentlySelectedBone) + { + bFoundBone = true; + BonePair.DeltaPose *= DeltaRotateMod; + break; + } + } + + if (!bFoundBone) + { + FBPVRHandPoseBonePair newBonePair; + newBonePair.BoneName = CurrentlySelectedBone; + newBonePair.DeltaPose *= DeltaRotateMod; + CurrentlyEditingComponent->CustomPoseDeltas.Add(newBonePair); + bFoundBone = true; + } + + if (bFoundBone) + { + NotifyPropertyModified(CurrentlyEditingComponent, FindFProperty(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + } + + //GEditor->RedrawLevelEditingViewports(true); + bHandled = true; + } + } + + return bHandled; +} + +void FHandSocketVisualizer::EndEditing() +{ + HandPropertyPath = FComponentPropertyPath(); + CurrentlySelectedBone = NAME_None; + CurrentlySelectedBoneIdx = INDEX_NONE; + TargetViewport = nullptr; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp new file mode 100644 index 0000000..2161551 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp @@ -0,0 +1,73 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "VRExpansionEditor.h" +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "Grippables/HandSocketComponent.h" +#include "PropertyEditorModule.h" +#include "HandSocketVisualizer.h" +#include "HandSocketComponentDetails.h" +#include "VRGlobalSettingsDetails.h" +#include "VRGlobalSettings.h" + + +IMPLEMENT_MODULE(FVRExpansionEditorModule, VRExpansionEditor); + +void FVRExpansionEditorModule::StartupModule() +{ + RegisterComponentVisualizer(UHandSocketComponent::StaticClass()->GetFName(), MakeShareable(new FHandSocketVisualizer)); + + // Register detail customizations + { + auto& PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register our customization to be used by a class 'UMyClass' or 'AMyClass'. Note the prefix must be dropped. + PropertyModule.RegisterCustomClassLayout( + UHandSocketComponent::StaticClass()->GetFName(), + FOnGetDetailCustomizationInstance::CreateStatic(&FHandSocketComponentDetails::MakeInstance) + ); + + PropertyModule.RegisterCustomClassLayout( + UVRGlobalSettings::StaticClass()->GetFName(), + FOnGetDetailCustomizationInstance::CreateStatic(&FVRGlobalSettingsDetails::MakeInstance) + ); + + PropertyModule.NotifyCustomizationModuleChanged(); + } + +} + +void FVRExpansionEditorModule::ShutdownModule() +{ + if (GUnrealEd != NULL) + { + // Iterate over all class names we registered for + for (FName ClassName : RegisteredComponentClassNames) + { + GUnrealEd->UnregisterComponentVisualizer(ClassName); + } + } + + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + auto& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(UHandSocketComponent::StaticClass()->GetFName()); + PropertyModule.UnregisterCustomClassLayout(UVRGlobalSettings::StaticClass()->GetFName()); + } +} + +void FVRExpansionEditorModule::RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr Visualizer) +{ + if (GUnrealEd != NULL) + { + GUnrealEd->RegisterComponentVisualizer(ComponentClassName, Visualizer); + } + + RegisteredComponentClassNames.Add(ComponentClassName); + + if (Visualizer.IsValid()) + { + Visualizer->OnRegister(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRGlobalSettingsDetails.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRGlobalSettingsDetails.cpp new file mode 100644 index 0000000..e5702be --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRGlobalSettingsDetails.cpp @@ -0,0 +1,151 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "VRGlobalSettingsDetails.h" +#include "VRGlobalSettings.h" +//#include "PropertyEditing.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +//#include "PropertyHandle.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "DetailCategoryBuilder.h" +#include "IDetailsView.h" + +//#include "Developer/AssetTools/Public/IAssetTools.h" +//#include "Developer/AssetTools/Public/AssetToolsModule.h" +//#include "Editor/ContentBrowser/Public/IContentBrowserSingleton.h" +//#include "Editor/ContentBrowser/Public/ContentBrowserModule.h" +//#include "AnimationUtils.h" + +#include "UObject/SavePackage.h" +#include "Misc/MessageDialog.h" +//#include "Widgets/Layout/SBorder.h" +//#include "Widgets/Layout/SSeparator.h" +//#include "Widgets/Layout/SUniformGridPanel.h" +//#include "Widgets/Input/SEditableTextBox.h" +#include "Editor.h" +#include "EditorStyleSet.h" +#include "Styling/CoreStyle.h" + +#include "Animation/AnimData/AnimDataModel.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/ARFilter.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" + +#define LOCTEXT_NAMESPACE "VRGlobalSettingsDetails" + + +TSharedRef< IDetailCustomization > FVRGlobalSettingsDetails::MakeInstance() +{ + return MakeShareable(new FVRGlobalSettingsDetails); +} + +void FVRGlobalSettingsDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + //DetailBuilder.HideCategory(FName("ComponentTick")); + //DetailBuilder.HideCategory(FName("GameplayTags")); + + + //TSharedPtr LockedLocationProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bDecoupleMeshPlacement)); + + //FSimpleDelegate OnLockedStateChangedDelegate = FSimpleDelegate::CreateSP(this, &FVRGlobalSettingsDetails::OnLockedStateUpdated, &DetailBuilder); + //LockedLocationProperty->SetOnPropertyValueChanged(OnLockedStateChangedDelegate); + + DetailBuilder.EditCategory("Utilities") + .AddCustomRow(NSLOCTEXT("VRGlobalSettingsDetails", "Tools", "Fix Invalid 5.2 Animation Assets")) + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("VRGlobalSettingsDetails", "Tools", "Fix Invalid 5.2 Animation Assets")) + ] + .ValueContent() + .MaxDesiredWidth(125.f) + .MinDesiredWidth(125.f) + [ + SNew(SButton) + .ContentPadding(2) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FVRGlobalSettingsDetails::OnCorrectInvalidAnimationAssets) + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("VRGlobalSettingsDetails", "Tools", "Fix Animation Assets")) + ] + ]; +} + +FReply FVRGlobalSettingsDetails::OnCorrectInvalidAnimationAssets() +{ + + // Load the asset registry module + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< FAssetRegistryModule >(FName("AssetRegistry")); + IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + TArray< FString > ContentPaths; + ContentPaths.Add(TEXT("/Game")); + AssetRegistry.ScanPathsSynchronous(ContentPaths); + + FARFilter Filter; + Filter.ClassPaths.Add(UAnimSequence::StaticClass()->GetClassPathName()); + //Filter.ClassNames.Add(UAnimSequence::StaticClass()->GetFName()); + Filter.bRecursiveClasses = true; + //if (!Path.IsEmpty()) + //{ + // Filter.PackagePaths.Add(*Path); + //} + Filter.bRecursivePaths = true; + + TArray< FAssetData > AssetList; + AssetRegistry.GetAssets(Filter, AssetList); + + // Iterate over retrieved blueprint assets + for (auto& Asset : AssetList) + { + // Check the anim sequence for invalid data + if (UAnimSequence* AnimSeq = Cast(Asset.GetAsset())) + { + IAnimationDataController& AnimController = AnimSeq->GetController(); + { + IAnimationDataController::FScopedBracket ScopedBracket(AnimController, LOCTEXT("FixAnimationAsset_VRE", "Fixing invalid anim sequences")); + const IAnimationDataModel* AnimModel = AnimController.GetModel(); + + FFrameRate FrameRate = AnimModel->GetFrameRate(); + //int32 NumFrames = AnimModel->GetNumberOfFrames(); + double FrameRateD = FrameRate.AsDecimal(); + + // I was saving with a below 1.0 frame rate and 1 frame + if (FrameRateD < 1.0f) + { + // We have an invalid frame rate for 5.2 + AnimController.SetFrameRate(FFrameRate(1, 1)); + AnimSeq->MarkPackageDirty(); + + UPackage* const Package = AnimSeq->GetOutermost(); + FString const PackageName = Package->GetName(); + FString const PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); + + double StartTime = FPlatformTime::Seconds(); + + FSavePackageArgs PackageArguments; + PackageArguments.SaveFlags = RF_Standalone; + PackageArguments.SaveFlags = SAVE_NoError; + UPackage::SavePackage(Package, NULL, *PackageFileName, PackageArguments); + //UPackage::SavePackage(Package, NULL, RF_Standalone, *PackageFileName, GError, nullptr, false, true, SAVE_NoError); + + double ElapsedTime = FPlatformTime::Seconds() - StartTime; + UE_LOG(LogAnimation, Log, TEXT("Animation re-saved %s in %0.2f seconds"), *PackageName, ElapsedTime); + } + + } + } + } + + + return FReply::Handled(); +} + + +#undef LOCTEXT_NAMESPACE diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h new file mode 100644 index 0000000..603abd8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h @@ -0,0 +1,83 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Grippables/HandSocketComponent.h" +#include "IDetailCustomization.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWindow.h" + +class IDetailLayoutBuilder; + +class FHandSocketComponentDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** IDetailCustomization interface */ + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + //void SortCategories(const TMap& AllCategoryMap); + + // The selected hand component + TWeakObjectPtr HandSocketComponent; + FReply OnUpdateSavePose(); + TWeakObjectPtr SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName); + + void OnLockedStateUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnLeftDominantUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnHandRelativeUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnUpdateShowMesh(IDetailLayoutBuilder* LayoutBuilder); + + FHandSocketComponentDetails() + { + } +}; + +class SCreateHandAnimationDlg : public SWindow +{ +public: + SLATE_BEGIN_ARGS(SCreateHandAnimationDlg) + { + } + + SLATE_ARGUMENT(FText, DefaultAssetPath) + SLATE_END_ARGS() + + SCreateHandAnimationDlg() + : UserResponse(EAppReturnType::Cancel) + { + } + + void Construct(const FArguments& InArgs); + +public: + /** Displays the dialog in a blocking fashion */ + EAppReturnType::Type ShowModal(); + + /** Gets the resulting asset path */ + FString GetAssetPath(); + + /** Gets the resulting asset name */ + FString GetAssetName(); + + /** Gets the resulting full asset path (path+'/'+name) */ + FString GetFullAssetPath(); + +protected: + void OnPathChange(const FString& NewPath); + void OnNameChange(const FText& NewName, ETextCommit::Type CommitInfo); + FReply OnButtonClick(EAppReturnType::Type ButtonID); + + bool ValidatePackage(); + + EAppReturnType::Type UserResponse; + FText AssetPath; + FText AssetName; + + static FText LastUsedAssetPath; + +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h new file mode 100644 index 0000000..d4fb707 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h @@ -0,0 +1,95 @@ +#pragma once + +#include "ComponentVisualizer.h" +#include "Grippables/HandSocketComponent.h" +#include "ActorEditorUtils.h" + +class FEditorViewportClient; + +/**Base class for clickable targeting editing proxies*/ +struct VREXPANSIONEDITOR_API HHandSocketVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + + HHandSocketVisProxy(const UActorComponent* InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + { + BoneIdx = 0; + TargetBoneName = NAME_None; + } + + uint32 BoneIdx; + FName TargetBoneName; +}; + +class VREXPANSIONEDITOR_API FHandSocketVisualizer : public FComponentVisualizer +{ +public: + FHandSocketVisualizer() + { + CurrentlySelectedBone = NAME_None; + CurrentlySelectedBoneIdx = INDEX_NONE; + HandPropertyPath = FComponentPropertyPath(); + TargetViewport = nullptr; + } + + virtual ~FHandSocketVisualizer() + { + + } + + UPROPERTY() + FComponentPropertyPath HandPropertyPath; + + FName CurrentlySelectedBone; + uint32 CurrentlySelectedBoneIdx; + + UPROPERTY() + FViewport* TargetViewport; + + UHandSocketComponent* GetCurrentlyEditingComponent() const + { + return Cast(HandPropertyPath.GetComponent());; + } + + const UHandSocketComponent* UpdateSelectedHandComponent(HComponentVisProxy* VisProxy) + { + const UHandSocketComponent* HandComp = CastChecked(VisProxy->Component.Get()); + UHandSocketComponent* OldHandComp = Cast(HandPropertyPath.GetComponent()); + AActor* OldOwningActor = HandPropertyPath.GetParentOwningActor(); + HandPropertyPath = FComponentPropertyPath(HandComp); + AActor* NewOwningActor = HandPropertyPath.GetParentOwningActor(); + + if (HandPropertyPath.IsValid()) + { + if (OldOwningActor != NewOwningActor || OldHandComp != HandComp) + { + // Reset selection state if we are selecting a different actor to the one previously selected + CurrentlySelectedBoneIdx = INDEX_NONE; + CurrentlySelectedBone = NAME_None; + } + + return HandComp; + } + + HandPropertyPath = FComponentPropertyPath(); + return nullptr; + } + + bool SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName); + + + bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override; + + bool IsVisualizingArchetype() const override; + + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; + virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; + virtual void EndEditing() override; + +private: + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h new file mode 100644 index 0000000..caf41a3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h @@ -0,0 +1,19 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Runtime/Core/Public/Modules/ModuleInterface.h" + +#include "ComponentVisualizer.h" + +class FVRExpansionEditorModule : public IModuleInterface +{ +public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + void RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr Visualizer); + + /** Array of component class names we have registered, so we know what to unregister afterwards */ + TArray RegisteredComponentClassNames; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRGlobalSettingsDetails.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRGlobalSettingsDetails.h new file mode 100644 index 0000000..6e5ee1e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRGlobalSettingsDetails.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWindow.h" + +class IDetailLayoutBuilder; + +class FVRGlobalSettingsDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** IDetailCustomization interface */ + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + FReply OnCorrectInvalidAnimationAssets(); + + void OnLockedStateUpdated(IDetailLayoutBuilder* LayoutBuilder); + + + FVRGlobalSettingsDetails() + { + } +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs new file mode 100644 index 0000000..92b9300 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class VRExpansionEditor : ModuleRules + { + + public VRExpansionEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + // ... add other public dependencies that you statically link with here ... + "Engine", + "Core", + "CoreUObject", + "VRExpansionPlugin", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "BlueprintGraph", + "AnimGraph", + "AnimGraphRuntime", + "SlateCore", + "Slate", + "InputCore", + "Engine", + "UnrealEd", + "EditorStyle", + "AssetRegistry" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp new file mode 100644 index 0000000..74e91f9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp @@ -0,0 +1,504 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "CharacterMovementCompTypes.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(CharacterMovementCompTypes) + +#include "VRBaseCharacterMovementComponent.h" +#include "VRBPDatatypes.h" +#include "VRBaseCharacter.h" +#include "VRRootComponent.h" +#include "VRPlayerController.h" + +FSavedMove_VRBaseCharacter::FSavedMove_VRBaseCharacter() : FSavedMove_Character() +{ + VRCapsuleLocation = FVector::ZeroVector; + LFDiff = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;// _None; +} + +uint8 FSavedMove_VRBaseCharacter::GetCompressedFlags() const +{ + // Fills in 01 and 02 for Jump / Crouch + uint8 Result = FSavedMove_Character::GetCompressedFlags(); + + // Not supporting custom movement mode directly at this time by replicating custom index + // We use 4 bits for this so a maximum of 16 elements + //Result |= (uint8)VRReplicatedMovementMode << 2; + + // This takes up custom_2 + /*if (bWantsToSnapTurn) + { + Result |= FLAG_SnapTurn; + }*/ + + // Reserved_1, and Reserved_2, Flag_Custom_0 and Flag_Custom_1 are used up + // By the VRReplicatedMovementMode packing + + + // only custom_2 and custom_3 are left currently + return Result; +} + +bool FSavedMove_VRBaseCharacter::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const +{ + FSavedMove_VRBaseCharacter* nMove = (FSavedMove_VRBaseCharacter*)NewMove.Get(); + + if (!nMove || (VRReplicatedMovementMode != nMove->VRReplicatedMovementMode)) + return false; + + if (!ConditionalValues.MoveActionArray.CanCombine() || !nMove->ConditionalValues.MoveActionArray.CanCombine()) + return false; + + if (!ConditionalValues.CustomVRInputVector.IsZero() || !nMove->ConditionalValues.CustomVRInputVector.IsZero()) + return false; + + if (!ConditionalValues.RequestedVelocity.IsZero() || !nMove->ConditionalValues.RequestedVelocity.IsZero()) + return false; + + // Hate this but we really can't combine if I am sending a new capsule height + if (!FMath::IsNearlyEqual(CapsuleHeight, nMove->CapsuleHeight)) + return false; + + if (!LFDiff.IsZero() && !nMove->LFDiff.IsZero() && !FVector::Coincident(LFDiff.GetSafeNormal(), nMove->LFDiff.GetSafeNormal(), AccelDotThresholdCombine)) + return false; + + return FSavedMove_Character::CanCombineWith(NewMove, Character, MaxDelta); +} + + +bool FSavedMove_VRBaseCharacter::IsImportantMove(const FSavedMovePtr& LastAckedMove) const +{ + // Auto important if toggled climbing + if (VRReplicatedMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX)//_None) + return true; + + if (!ConditionalValues.CustomVRInputVector.IsZero()) + return true; + + if (!ConditionalValues.RequestedVelocity.IsZero()) + return true; + + if (ConditionalValues.MoveActionArray.MoveActions.Num() > 0) + return true; + + // #TODO: What to do here? + // This is debatable, however it will ALWAYS be non zero realistically and only really effects step ups for the most part + //if (!LFDiff.IsNearlyZero()) + //return true; + + // Else check parent class + return FSavedMove_Character::IsImportantMove(LastAckedMove); +} + +void FSavedMove_VRBaseCharacter::SetInitialPosition(ACharacter* C) +{ + // See if we can get the VR capsule location + //if (AVRBaseCharacter * VRC = Cast(C)) + //{ + if (UVRBaseCharacterMovementComponent* moveComp = Cast(C->GetMovementComponent())) + { + + // Saving this out early because it will be wiped before the PostUpdate gets the values + //ConditionalValues.MoveAction.MoveAction = moveComp->MoveAction.MoveAction; + + VRReplicatedMovementMode = moveComp->VRReplicatedMovementMode; + + if (moveComp->HasRequestedVelocity()) + ConditionalValues.RequestedVelocity = moveComp->RequestedVelocity; + else + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + + // Throw out the Z value of the headset, its not used anyway for movement + // Instead, re-purpose it to be the capsule half height + if (AVRBaseCharacter* BaseChar = Cast(C)) + { + if (BaseChar->VRReplicateCapsuleHeight) + CapsuleHeight = BaseChar->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + else + CapsuleHeight = 0.0f; + } + else + CapsuleHeight = 0.0f; + } + else + { + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + } + //} + //else + //{ + // VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + // ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + //} + + FSavedMove_Character::SetInitialPosition(C); +} + +void FSavedMove_VRBaseCharacter::CombineWith(const FSavedMove_Character* OldMove, ACharacter* InCharacter, APlayerController* PC, const FVector& OldStartLocation) +{ + UCharacterMovementComponent* CharMovement = InCharacter->GetCharacterMovement(); + + // to combine move, first revert pawn position to PendingMove start position, before playing combined move on client + CharMovement->UpdatedComponent->SetWorldLocationAndRotation(OldStartLocation, OldMove->StartRotation, false, nullptr, CharMovement->GetTeleportType()); + CharMovement->Velocity = OldMove->StartVelocity; + + CharMovement->SetBase(OldMove->StartBase.Get(), OldMove->StartBoneName); + CharMovement->CurrentFloor = OldMove->StartFloor; + + // Now that we have reverted to the old position, prepare a new move from that position, + // using our current velocity, acceleration, and rotation, but applied over the combined time from the old and new move. + + // Combine times for both moves + DeltaTime += OldMove->DeltaTime; + + //FSavedMove_VRBaseCharacter * BaseSavedMove = (FSavedMove_VRBaseCharacter *)NewMove.Get(); + FSavedMove_VRBaseCharacter* BaseSavedMovePending = (FSavedMove_VRBaseCharacter*)OldMove; + + if (/*BaseSavedMove && */BaseSavedMovePending) + { + LFDiff.X += BaseSavedMovePending->LFDiff.X; + LFDiff.Y += BaseSavedMovePending->LFDiff.Y; + } + + // Roll back jump force counters. SetInitialPosition() below will copy them to the saved move. + // Changes in certain counters like JumpCurrentCount don't allow move combining, so no need to roll those back (they are the same). + InCharacter->JumpForceTimeRemaining = OldMove->JumpForceTimeRemaining; + InCharacter->JumpKeyHoldTime = OldMove->JumpKeyHoldTime; + + // Merge if we had valid mergable move actions + ConditionalValues.MoveActionArray.MoveActions.Append(BaseSavedMovePending->ConditionalValues.MoveActionArray.MoveActions); +} + +void FSavedMove_VRBaseCharacter::PostUpdate(ACharacter* C, EPostUpdateMode PostUpdateMode) +{ + FSavedMove_Character::PostUpdate(C, PostUpdateMode); + + // See if we can get the VR capsule location + //if (AVRBaseCharacter * VRC = Cast(C)) + //{ + if (UVRBaseCharacterMovementComponent* moveComp = Cast(C->GetMovementComponent())) + { + ConditionalValues.CustomVRInputVector = moveComp->CustomVRInputVector; + ConditionalValues.MoveActionArray = moveComp->MoveActionArray; + moveComp->MoveActionArray.Clear(); + } + //} + /*if (ConditionalValues.MoveAction.MoveAction != EVRMoveAction::VRMOVEACTION_None) + { + // See if we can get the VR capsule location + if (AVRBaseCharacter * VRC = Cast(C)) + { + if (UVRBaseCharacterMovementComponent * moveComp = Cast(VRC->GetMovementComponent())) + { + // This is cleared out in perform movement so I need to save it before applying below + EVRMoveAction tempAction = ConditionalValues.MoveAction.MoveAction; + ConditionalValues.MoveAction = moveComp->MoveAction; + ConditionalValues.MoveAction.MoveAction = tempAction; + } + else + { + ConditionalValues.MoveAction.Clear(); + } + } + else + { + ConditionalValues.MoveAction.Clear(); + } + }*/ +} + +void FSavedMove_VRBaseCharacter::Clear() +{ + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;// None; + + VRCapsuleLocation = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + LFDiff = FVector::ZeroVector; + CapsuleHeight = 0.0f; + + ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + ConditionalValues.MoveActionArray.Clear(); + //ConditionalValues.MoveAction.Clear(); + + FSavedMove_Character::Clear(); +} + +void FSavedMove_VRBaseCharacter::PrepMoveFor(ACharacter* Character) +{ + UVRBaseCharacterMovementComponent* BaseCharMove = Cast(Character->GetCharacterMovement()); + + if (BaseCharMove) + { + BaseCharMove->MoveActionArray = ConditionalValues.MoveActionArray; + //BaseCharMove->MoveAction = ConditionalValues.MoveAction; + BaseCharMove->CustomVRInputVector = ConditionalValues.CustomVRInputVector;//this->CustomVRInputVector; + BaseCharMove->VRReplicatedMovementMode = this->VRReplicatedMovementMode; + } + + if (!ConditionalValues.RequestedVelocity.IsZero()) + { + BaseCharMove->RequestedVelocity = ConditionalValues.RequestedVelocity; + BaseCharMove->SetHasRequestedVelocity(true); + } + else + { + BaseCharMove->SetHasRequestedVelocity(false); + } + + FSavedMove_Character::PrepMoveFor(Character); +} + +FVRCharacterScopedMovementUpdate::FVRCharacterScopedMovementUpdate(USceneComponent* Component, EScopedUpdate::Type ScopeBehavior, bool bRequireOverlapsEventFlagToQueueOverlaps) + : FScopedMovementUpdate(Component, ScopeBehavior, bRequireOverlapsEventFlagToQueueOverlaps) +{ + UVRRootComponent* RootComponent = Cast(Owner); + if (RootComponent) + { + InitialVRTransform = RootComponent->OffsetComponentToWorld; + } +} + +void FVRCharacterScopedMovementUpdate::RevertMove() +{ + bool bTransformIsDirty = IsTransformDirty(); + + FScopedMovementUpdate::RevertMove(); + + UVRRootComponent* RootComponent = Cast(Owner); + if (RootComponent) + { + // If the base class was going to miss bad overlaps, ie: the offsetcomponent to world is different but the transform isn't + if (!bTransformIsDirty && !IsDeferringUpdates() && !InitialVRTransform.Equals(RootComponent->OffsetComponentToWorld)) + { + RootComponent->UpdateOverlaps(); + } + + // Fix offset + RootComponent->GenerateOffsetToWorld(); + } +} + + +FVRCharacterNetworkMoveData::FVRCharacterNetworkMoveData() : FCharacterNetworkMoveData() +{ + VRCapsuleLocation = FVector::ZeroVector; + LFDiff = FVector::ZeroVector; + CapsuleHeight = 0.f; + VRCapsuleRotation = 0.f; + ReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; +} + +FVRCharacterNetworkMoveData::~FVRCharacterNetworkMoveData() +{ +} + +void FVRCharacterNetworkMoveData::ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) +{ + // Handles the movement base itself now + FCharacterNetworkMoveData::ClientFillNetworkMoveData(ClientMove, MoveType); + + // I know that we overloaded this, so it should be our base type + if (const FSavedMove_VRBaseCharacter* SavedMove = (const FSavedMove_VRBaseCharacter*)(&ClientMove)) + { + ReplicatedMovementMode = SavedMove->VRReplicatedMovementMode; + ConditionalMoveReps = SavedMove->ConditionalValues; + + // #TODO: Roll these into the conditionals + VRCapsuleLocation = SavedMove->VRCapsuleLocation; + LFDiff = SavedMove->LFDiff; + CapsuleHeight = SavedMove->CapsuleHeight; + VRCapsuleRotation = FRotator::CompressAxisToShort(SavedMove->VRCapsuleRotation.Yaw); + } +} + +bool FVRCharacterNetworkMoveData::Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) +{ + NetworkMoveType = MoveType; + + bool bLocalSuccess = true; + const bool bIsSaving = Ar.IsSaving(); + + Ar << TimeStamp; + + // Handle switching the acceleration rep + // Can't use SerializeOptionalValue here as I don't want to bitwise compare floats + bool bRepAccel = bIsSaving ? !Acceleration.IsNearlyZero() : false; + Ar.SerializeBits(&bRepAccel, 1); + + if (bRepAccel) + { + Acceleration.NetSerialize(Ar, PackageMap, bLocalSuccess); + } + else + { + if (!bIsSaving) + { + Acceleration = FVector::ZeroVector; + } + } + + //Location.NetSerialize(Ar, PackageMap, bLocalSuccess); + + uint16 Yaw = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Yaw) : 0; + uint16 Pitch = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Pitch) : 0; + uint16 Roll = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Roll) : 0; + bool bRepYaw = Yaw != 0; + + ACharacter* CharacterOwner = CharacterMovement.GetCharacterOwner(); + + bool bCanRepRollAndPitch = (CharacterOwner && (CharacterOwner->bUseControllerRotationRoll || CharacterOwner->bUseControllerRotationPitch)); + bool bRepRollAndPitch = bCanRepRollAndPitch && (Roll != 0 || Pitch != 0); + Ar.SerializeBits(&bRepRollAndPitch, 1); + + if (bRepRollAndPitch) + { + // Reversed the order of these + uint32 Rotation32 = 0; + uint32 Yaw32 = bIsSaving ? Yaw : 0; + + if (bIsSaving) + { + Rotation32 = (((uint32)Roll) << 16) | ((uint32)Pitch); + Ar.SerializeIntPacked(Rotation32); + } + else + { + Ar.SerializeIntPacked(Rotation32); + + // Reversed the order of these so it costs less to replicate + Pitch = (Rotation32 & 65535); + Roll = (Rotation32 >> 16); + } + } + + uint32 Yaw32 = bIsSaving ? Yaw : 0; + + Ar.SerializeBits(&bRepYaw, 1); + if (bRepYaw) + { + Ar.SerializeIntPacked(Yaw32); + Yaw = (uint16)Yaw32; + } + + if (!bIsSaving) + { + ControlRotation.Yaw = bRepYaw ? FRotator::DecompressAxisFromShort(Yaw) : 0; + ControlRotation.Pitch = bRepRollAndPitch ? FRotator::DecompressAxisFromShort(Pitch) : 0; + ControlRotation.Roll = bRepRollAndPitch ? FRotator::DecompressAxisFromShort(Roll) : 0; + } + + // ControlRotation : FRotator handles each component zero/non-zero test; it uses a single signal bit for zero/non-zero, and uses 16 bits per component if non-zero. + //ControlRotation.NetSerialize(Ar, PackageMap, bLocalSuccess); + + SerializeOptionalValue(bIsSaving, Ar, CompressedMoveFlags, 0); + SerializeOptionalValue(bIsSaving, Ar, MovementMode, MOVE_Walking); + VRCapsuleLocation.NetSerialize(Ar, PackageMap, bLocalSuccess); + Ar << VRCapsuleRotation; + + // Location is only used for error checking, so only save for the final move. + //if (MoveType == ENetworkMoveType::NewMove) + //{ + Location.NetSerialize(Ar, PackageMap, bLocalSuccess); + //} + + // Movement base needs to always send now since they allow for relative based velocity + SerializeOptionalValue(bIsSaving, Ar, MovementBase, nullptr); + SerializeOptionalValue(bIsSaving, Ar, MovementBaseBoneName, NAME_None); + + bool bHasReplicatedMovementMode = ReplicatedMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX; + Ar.SerializeBits(&bHasReplicatedMovementMode, 1); + + if (bHasReplicatedMovementMode) + { + // Increased to 6 bits for 64 total elements instead of 16 + Ar.SerializeBits(&ReplicatedMovementMode, 6); + } + else if(!bIsSaving) + { + ReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; + } + + // Rep out our custom move settings + ConditionalMoveReps.NetSerialize(Ar, PackageMap, bLocalSuccess); + + //VRCapsuleLocation.NetSerialize(Ar, PackageMap, bLocalSuccess); + if (AVRBaseCharacter* VRChar = Cast(CharacterOwner)) + { + if (!VRChar->bRetainRoomscale) + { + SerializePackedVector<10000, 32>(LFDiff, Ar); + } + else + { + SerializePackedVector<100, 30>(LFDiff, Ar); + } + } + else + { + SerializePackedVector<100, 30>(LFDiff, Ar); + } + + bool bHasCapsuleHeight = CapsuleHeight > 0.f; + Ar.SerializeBits(&bHasCapsuleHeight, 1); + + if (bHasCapsuleHeight) + { + // This is 0.0 - 512.0, using compression to get it smaller, 8 bits = max 256 + 1 bit for sign and 7 bits precision for 128 / full 2 digit precision + if (Ar.IsSaving()) + { + WriteFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + else + { + ReadFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + } + + //LFDiff.NetSerialize(Ar, PackageMap, bLocalSuccess); + //Ar << VRCapsuleRotation; + + return !Ar.IsError(); +} + + +void FVRCharacterMoveResponseDataContainer::ServerFillResponseData(const UCharacterMovementComponent& CharacterMovement, const FClientAdjustment& PendingAdjustment) +{ + FCharacterMoveResponseDataContainer::ServerFillResponseData(CharacterMovement, PendingAdjustment); + + if (const UVRBaseCharacterMovementComponent* BaseMovecomp = Cast(&CharacterMovement)) + { + // #TODO: This is set in the pending adjustment now in 5.1 + //bHasRotation = CharacterMovement.ShouldCorrectRotation(); + bHasRotation = !BaseMovecomp->bUseClientControlRotation; + } +} + +FScopedMeshBoneUpdateOverrideVR::FScopedMeshBoneUpdateOverrideVR(USkeletalMeshComponent* Mesh, EKinematicBonesUpdateToPhysics::Type OverrideSetting) + : MeshRef(Mesh) +{ + if (MeshRef) + { + // Save current state. + SavedUpdateSetting = MeshRef->KinematicBonesUpdateType; + // Override bone update setting. + MeshRef->KinematicBonesUpdateType = OverrideSetting; + } +} + +FScopedMeshBoneUpdateOverrideVR::~FScopedMeshBoneUpdateOverrideVR() +{ + if (MeshRef) + { + // Restore bone update flag. + MeshRef->KinematicBonesUpdateType = SavedUpdateSetting; + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp new file mode 100644 index 0000000..96fa8f5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp @@ -0,0 +1,8389 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "GripMotionControllerComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GripMotionControllerComponent) + +#include "VRExpansionFunctionLibrary.h" +#include "IHeadMountedDisplay.h" +#include "HeadMountedDisplayTypes.h" +#include "Misc/ScopeLock.h" +#include "Net/UnrealNetwork.h" +#include "PrimitiveSceneInfo.h" +#include "Engine/World.h" +#include "GameFramework/WorldSettings.h" +#include "IXRSystemAssets.h" +#include "Components/StaticMeshComponent.h" +#include "MotionDelayBuffer.h" +#include "UObject/VRObjectVersion.h" +#include "UObject/UObjectGlobals.h" // for FindObject<> +#include "IXRTrackingSystem.h" +#include "IXRSystemAssets.h" +#include "DrawDebugHelpers.h" +#include "TimerManager.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "VRRootComponent.h" +#include "VRGlobalSettings.h" +#include "Math/DualQuat.h" +#include "IIdentifiableXRDevice.h" // for FXRDeviceId +#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName() +//#include "XRDeviceVisualizationComponent.h" // For visualization component + +#include "Physics/Experimental/PhysScene_Chaos.h" + +#include "GripScripts/GS_Default.h" +#include "GripScripts/GS_LerpToHand.h" + +#include "PhysicsPublic.h" +#include "PhysicsEngine/BodySetup.h" +#include "PhysicsEngine/ConstraintDrives.h" +#include "PhysicsReplication.h" +#include "PhysicsEngine/PhysicsAsset.h" + +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/PBDJointConstraintTypes.h" +#include "Chaos/PBDConstraintBaseData.h" +#include "Chaos/PBDJointConstraintData.h" +#include "Chaos/Sphere.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "Chaos/ChaosConstraintSettings.h" +#include "Chaos/PhysicsObject.h" +#include "PhysicsEngine/PhysicsObjectExternalInterface.h" +#include "Chaos/PhysicsObjectInterface.h" + +#include "Misc/CollisionIgnoreSubsystem.h" + +#include "Features/IModularFeatures.h" + +DEFINE_LOG_CATEGORY(LogVRMotionController); +//For UE4 Profiler ~ Stat +DECLARE_CYCLE_STAT(TEXT("TickGrip ~ TickingGrip"), STAT_TickGrip, STATGROUP_TickGrip); +DECLARE_CYCLE_STAT(TEXT("GetGripWorldTransform ~ GettingTransform"), STAT_GetGripTransform, STATGROUP_TickGrip); + +// MAGIC NUMBERS +// Constraint multipliers for angular, to avoid having to have two sets of stiffness/damping variables +const float ANGULAR_STIFFNESS_MULTIPLIER = 1.5f; +const float ANGULAR_DAMPING_MULTIPLIER = 1.4f; +const float ANGULAR_STIFFNESS_MULTIPLIER_CHAOS = 0.45f; +const float ANGULAR_DAMPING_MULTIPLIER_CHAOS = 0.45f; + +// Multiplier for the Interactive Hybrid With Physics grip - When not colliding increases stiffness by this value +const float HYBRID_PHYSICS_GRIP_MULTIPLIER = 10.0f; + +namespace { + /** This is to prevent destruction of motion controller components while they are + in the middle of being accessed by the render thread */ + FCriticalSection CritSect; + +} // anonymous namespace + +namespace GripUEMotionController { + // A scoped lock that must be explicitly locked and will unlock upon destruction if locked. + // Convenient if you only sometimes want to lock and the scopes are complicated. + class FScopeLockOptional + { + public: + FScopeLockOptional() + { + } + + void Lock(FCriticalSection* InSynchObject) + { + SynchObject = InSynchObject; + SynchObject->Lock(); + } + + /** Destructor that performs a release on the synchronization object. */ + ~FScopeLockOptional() + { + Unlock(); + } + + void Unlock() + { + if (SynchObject) + { + SynchObject->Unlock(); + SynchObject = nullptr; + } + } + + private: + /** Copy constructor( hidden on purpose). */ + FScopeLockOptional(const FScopeLockOptional& InScopeLock); + + /** Assignment operator (hidden on purpose). */ + FScopeLockOptional& operator=(FScopeLockOptional& InScopeLock) + { + return *this; + } + + private: + + // Holds the synchronization object to aggregate and scope manage. + FCriticalSection* SynchObject = nullptr; + }; +} + + // CVars +namespace GripMotionControllerCvars +{ + static int32 DrawDebugGripCOM = 0; + FAutoConsoleVariableRef CVarDrawCOMDebugSpheres( + TEXT("vr.DrawDebugCenterOfMassForGrips"), + DrawDebugGripCOM, + TEXT("When on, will draw debug speheres for physics grips COM.\n") + TEXT("0: Disable, 1: Enable"), + ECVF_Default); +} + + //============================================================================= +UGripMotionControllerComponent::UGripMotionControllerComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + PrimaryComponentTick.bTickEvenWhenPaused = true; + + PlayerIndex = 0; + MotionSource = IMotionController::LeftHandSourceId; + //Hand = EControllerHand::Left; + bDisableLowLatencyUpdate = false; + bHasAuthority = false; + bIgnoreTrackingStatus = false; + bUseWithoutTracking = false; + ClientAuthConflictResolutionMethod = EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_First; + bAlwaysSendTickGrip = false; + bAutoActivate = true; + + SetIsReplicatedByDefault(true); + + // Epic never initializes this variable, so I need to + CurrentTrackingStatus = ETrackingStatus::NotTracked; + + // Default 100 htz update rate, same as the 100htz update rate of rep_notify, will be capped to 90/45 though because of vsync on HMD + //bReplicateControllerTransform = true; + ControllerNetUpdateRate = 100.0f; // 100 htz is default + ControllerNetUpdateCount = 0.0f; + bReplicateWithoutTracking = false; + bLerpingPosition = false; + bSmoothReplicatedMotion = false; + bReppedOnce = false; + bScaleTracking = false; + TrackingScaler = FVector(1.0f); + bLimitMinHeight = false; + MinimumHeight = 0.0f; + bLimitMaxHeight = false; + MaximumHeight = 240.0f; + //bOffsetByHMD = false; + bLeashToHMD = false; + LeashRange = 300.0f; + bConstrainToPivot = false; + + bSmoothHandTracking = false; + bWasSmoothingHand = false; + bSmoothWithEuroLowPassFunction = true; + LastSmoothRelativeTransform = FTransform::Identity; + SmoothingSpeed = 20.0f; + EuroSmoothingParams.MinCutoff = 0.1f; + EuroSmoothingParams.DeltaCutoff = 10.f; + EuroSmoothingParams.CutoffSlope = 10.f; + + bIsPostTeleport = false; + + GripIDIncrementer = INVALID_VRGRIP_ID; + + // Pivot Variables + CustomPivotComponentSocketName = NAME_None; + bSkipPivotTransformAdjustment = false; + + bOffsetByControllerProfile = true; + CurrentControllerProfileTransform = FTransform::Identity; + + DefaultGripScript = nullptr; + DefaultGripScriptClass = UGS_Default::StaticClass(); + //DisplayComponentReference = nullptr; + + VelocityCalculationType = EVRVelocityType::VRLOCITY_Default; + LastRelativePosition = FTransform::Identity; + bSampleVelocityInWorldSpace = false; + VelocitySamples = 30.f; + + bProjectNonSimulatingGrips = false; + EndPhysicsTickFunction.TickGroup = TG_EndPhysics; + EndPhysicsTickFunction.bCanEverTick = true; + EndPhysicsTickFunction.bStartWithTickEnabled = false; +} + +void UGripMotionControllerComponent::RegisterEndPhysicsTick(bool bRegister) +{ + if (bRegister != EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bRegister) + { + if (SetupActorComponentTickFunction(&EndPhysicsTickFunction)) + { + EndPhysicsTickFunction.Target = this; + // Make sure our EndPhysicsTick gets called after physics simulation is finished + UWorld* World = GetWorld(); + if (World != nullptr) + { + EndPhysicsTickFunction.AddPrerequisite(World, World->EndPhysicsTickFunction); + } + } + } + else + { + EndPhysicsTickFunction.UnRegisterTickFunction(); + } + } +} + +void UGripMotionControllerComponent::EndPhysicsTickComponent(FGripComponentEndPhysicsTickFunction& ThisTickFunction) +{ + + if (!IsValid(this)) + return; + + // Now check if we should turn off any post physics ticking + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform().Inverse(); + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (!LocallyGrippedObjects[i].GrippedObject || !IsValid(LocallyGrippedObjects[i].GrippedObject)) + continue; // Skip, don't process this + + if (LocallyGrippedObjects[i].GrippedObject && IsValid(LocallyGrippedObjects[i].GrippedObject) && LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bSampleRelativeTransform = bProjectNonSimulatingGrips; + + if (!bSampleRelativeTransform) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(LocallyGrippedObjects[i].GrippedObject); + bSampleRelativeTransform = TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation; + } + + if (bSampleRelativeTransform) + { + switch(LocallyGrippedObjects[i].GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* Actor = Cast(LocallyGrippedObjects[i].GrippedObject)) + { + if (UPrimitiveComponent* root = Cast(Actor->GetRootComponent())) + { + LocallyGrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + LocallyGrippedObjects[i].bSetLastWorldTransform = true; + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* root = Cast(LocallyGrippedObjects[i].GrippedObject)) + { + LocallyGrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + LocallyGrippedObjects[i].bSetLastWorldTransform = true; + } + }break; + } + } + } + } + + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (!GrippedObjects[i].GrippedObject || !IsValid(GrippedObjects[i].GrippedObject)) + continue; // Skip, don't process this + + if (GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bSampleRelativeTransform = bProjectNonSimulatingGrips; + + if (!bSampleRelativeTransform) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(GrippedObjects[i].GrippedObject); + bSampleRelativeTransform = TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation; + } + + if (bSampleRelativeTransform) + { + switch (GrippedObjects[i].GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* Actor = Cast(GrippedObjects[i].GrippedObject)) + { + if (UPrimitiveComponent* root = Cast(Actor->GetRootComponent())) + { + GrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + GrippedObjects[i].bSetLastWorldTransform = true; + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* root = Cast(GrippedObjects[i].GrippedObject)) + { + GrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + GrippedObjects[i].bSetLastWorldTransform = true; + } + }break; + } + } + } + } +} + +void FGripComponentEndPhysicsTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) +{ + QUICK_SCOPE_CYCLE_COUNTER(FGripComponentEndPhysicsTickFunction_ExecuteTick); + CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Physics); + + if (Target && IsValid(Target)) + { + FActorComponentTickFunction::ExecuteTickHelper(Target, /*bTickInEditor=*/ false, DeltaTime, TickType, [this](float DilatedTime) + { + Target->EndPhysicsTickComponent(*this); + }); + } +} + +FString FGripComponentEndPhysicsTickFunction::DiagnosticMessage() +{ + return TEXT("GripComponentEndPhysicsTickFunction"); +} + +FName FGripComponentEndPhysicsTickFunction::DiagnosticContext(bool bDetailed) +{ + return FName(TEXT("GripComponentEndPhysicsTick")); +} + + +//============================================================================= +UGripMotionControllerComponent::~UGripMotionControllerComponent() +{ + // Moved view extension destruction to BeginDestroy like the new controllers + // Epic had it listed as a crash in the private bug tracker I guess. +} + +void UGripMotionControllerComponent::NewControllerProfileLoaded() +{ + GetCurrentProfileTransform(false); +} + +void UGripMotionControllerComponent::GetCurrentProfileTransform(bool bBindToNoticationDelegate) +{ + if (bOffsetByControllerProfile) + { + UVRGlobalSettings* VRSettings = GetMutableDefault(); + + if (VRSettings == nullptr) + return; + + EControllerHand HandType; + this->GetHandType(HandType); + + FTransform NewControllerProfileTransform = FTransform::Identity; + + if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand || !VRSettings->bUseSeperateHandTransforms) + { + NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransform; + } + else if (HandType == EControllerHand::Right) + { + NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransformRight; + } + + if (bBindToNoticationDelegate && !NewControllerProfileEvent_Handle.IsValid()) + { + NewControllerProfileEvent_Handle = VRSettings->OnControllerProfileChangedEvent.AddUObject(this, &UGripMotionControllerComponent::NewControllerProfileLoaded); + } + + if (!NewControllerProfileTransform.Equals(CurrentControllerProfileTransform)) + { + FTransform OriginalControllerProfileTransform = CurrentControllerProfileTransform; + CurrentControllerProfileTransform = NewControllerProfileTransform; + + // Auto adjust for FPS testing pawns + if (!bTracked && bUseWithoutTracking) + { + this->SetRelativeTransform(CurrentControllerProfileTransform * (OriginalControllerProfileTransform.Inverse() * this->GetRelativeTransform())); + } + + OnControllerProfileTransformChanged.Broadcast(CurrentControllerProfileTransform.Inverse() * OriginalControllerProfileTransform, CurrentControllerProfileTransform); + } + } +} + +void UGripMotionControllerComponent::InitializeComponent() +{ + Super::InitializeComponent(); + + if (!DefaultGripScript && DefaultGripScriptClass) + DefaultGripScript = NewObject(this, DefaultGripScriptClass); //DefaultGripScriptClass.GetDefaultObject(); + else + DefaultGripScript = NewObject(this, UGS_Default::StaticClass()); +} + +void UGripMotionControllerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // Cancel end physics tick + RegisterEndPhysicsTick(false); + + if (NewControllerProfileEvent_Handle.IsValid()) + { + UVRGlobalSettings* VRSettings = GetMutableDefault(); + if (VRSettings != nullptr) + { + VRSettings->OnControllerProfileChangedEvent.Remove(NewControllerProfileEvent_Handle); + NewControllerProfileEvent_Handle.Reset(); + } + } + + for (int i = 0; i < GrippedObjects.Num(); i++) + { + DestroyPhysicsHandle(GrippedObjects[i]); + + if (/*HasGripAuthority(GrippedObjects[i]) || */IsServer()) + { + DropObjectByInterface(nullptr, GrippedObjects[i].GripID); + } + else + { + if (IsValid(GrippedObjects[i].GrippedObject)) + { + bool bSimulateOnDrop = true; + if (GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(GrippedObjects[i].GrippedObject); + } + + NotifyDrop(GrippedObjects[i], bSimulateOnDrop); + } + } + } + GrippedObjects.Empty(); + + for (int i = 0; i < LocallyGrippedObjects.Num(); i++) + { + DestroyPhysicsHandle(LocallyGrippedObjects[i]); + + if (/*HasGripAuthority(LocallyGrippedObjects[i]) || */IsServer()) + { + DropObjectByInterface(nullptr, LocallyGrippedObjects[i].GripID); + } + else + { + if (IsValid(LocallyGrippedObjects[i].GrippedObject)) + { + bool bSimulateOnDrop = true; + if (LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(LocallyGrippedObjects[i].GrippedObject); + } + + NotifyDrop(LocallyGrippedObjects[i], bSimulateOnDrop); + } + } + } + LocallyGrippedObjects.Empty(); + + for (int i = 0; i < PhysicsGrips.Num(); i++) + { + DestroyPhysicsHandle(&PhysicsGrips[i]); + } + PhysicsGrips.Empty(); + + // Clear any timers that we are managing + if (UWorld * myWorld = GetWorld()) + { + myWorld->GetTimerManager().ClearAllTimersForObject(this); + } + + ObjectsWaitingForSocketUpdate.Empty(); +} + +void UGripMotionControllerComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void UGripMotionControllerComponent::BeginDestroy() +{ + Super::BeginDestroy(); + + if (GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent and allowing the destructor to continue + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } +} + +void UGripMotionControllerComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +void UGripMotionControllerComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ + + // Don't bother updating this stuff if we aren't local or using them + if (bHasAuthority && !bDisableLowLatencyUpdate && IsActive()) + { + LateUpdateParams.GripRenderThreadRelativeTransform = GetRelativeTransform(); + LateUpdateParams.GripRenderThreadComponentScale = GetComponentScale(); + LateUpdateParams.GripRenderThreadProfileTransform = CurrentControllerProfileTransform; + LateUpdateParams.GripRenderThreadLastLocationForLateUpdate = LastLocationForLateUpdate; + + LateUpdateParams.bRenderSmoothHandTracking = bSmoothHandTracking; + if (LateUpdateParams.bRenderSmoothHandTracking) + { + if (UWorld* world = GetWorld()) + { + LateUpdateParams.RenderLastDeltaTime = world->GetDeltaSeconds(); + } + + LateUpdateParams.bRenderSmoothWithEuroLowPassFunction = bSmoothWithEuroLowPassFunction; + + if (LateUpdateParams.bRenderSmoothWithEuroLowPassFunction) + { + LateUpdateParams.RenderEuroSmoothingParams = EuroSmoothingParams; + } + else + { + LateUpdateParams.RenderSmoothingSpeed = SmoothingSpeed; + LateUpdateParams.RenderLastSmoothRelativeTransform = LastSmoothRelativeTransform; + } + } + } + + Super::Super::CreateRenderState_Concurrent(Context); +} + +void UGripMotionControllerComponent::SendRenderTransform_Concurrent() +{ + // Don't bother updating this stuff if we aren't local or using them + if (bHasAuthority && !bDisableLowLatencyUpdate && IsActive()) + { + struct FPrimitiveUpdateRenderThreadRelativeTransformParams + { + FRenderTrackingParams LateUpdateParams; + }; + + FPrimitiveUpdateRenderThreadRelativeTransformParams UpdateParams; + UpdateParams.LateUpdateParams.GripRenderThreadRelativeTransform = GetRelativeTransform(); + UpdateParams.LateUpdateParams.GripRenderThreadComponentScale = GetComponentScale(); + UpdateParams.LateUpdateParams.GripRenderThreadProfileTransform = CurrentControllerProfileTransform; + UpdateParams.LateUpdateParams.GripRenderThreadLastLocationForLateUpdate = LastLocationForLateUpdate; + + UpdateParams.LateUpdateParams.bRenderSmoothHandTracking = bSmoothHandTracking; + if (UpdateParams.LateUpdateParams.bRenderSmoothHandTracking) + { + if (UWorld* world = GetWorld()) + { + UpdateParams.LateUpdateParams.RenderLastDeltaTime = world->GetDeltaSeconds(); + } + + UpdateParams.LateUpdateParams.bRenderSmoothWithEuroLowPassFunction = bSmoothWithEuroLowPassFunction; + + if (UpdateParams.LateUpdateParams.bRenderSmoothWithEuroLowPassFunction) + { + UpdateParams.LateUpdateParams.RenderEuroSmoothingParams = EuroSmoothingParams; + } + else + { + UpdateParams.LateUpdateParams.RenderSmoothingSpeed = SmoothingSpeed; + UpdateParams.LateUpdateParams.RenderLastSmoothRelativeTransform = LastSmoothRelativeTransform; + } + } + + ENQUEUE_RENDER_COMMAND(UpdateRTRelativeTransformCommand)( + [UpdateParams, this](FRHICommandListImmediate& RHICmdList) + { + LateUpdateParams = UpdateParams.LateUpdateParams; + }); + } + + // Skip bases motion controllers implementation, we don't want to double update to the render thread + Super::Super::SendRenderTransform_Concurrent(); +} + +FBPActorPhysicsHandleInformation * UGripMotionControllerComponent::GetPhysicsGrip(const FBPActorGripInformation & GripInfo) +{ + return PhysicsGrips.FindByKey(GripInfo); +} + +FBPActorPhysicsHandleInformation* UGripMotionControllerComponent::GetPhysicsGrip(const uint8 GripID) +{ + return PhysicsGrips.FindByKey(GripID); +} + +bool UGripMotionControllerComponent::GetPhysicsGripIndex(const FBPActorGripInformation & GripInfo, int & index) +{ + index = PhysicsGrips.IndexOfByKey(GripInfo); + return index != INDEX_NONE; +} + +FBPActorPhysicsHandleInformation * UGripMotionControllerComponent::CreatePhysicsGrip(const FBPActorGripInformation & GripInfo) +{ + FBPActorPhysicsHandleInformation * HandleInfo = PhysicsGrips.FindByKey(GripInfo); + + if (HandleInfo) + { + DestroyPhysicsHandle(HandleInfo); + return HandleInfo; + } + + FBPActorPhysicsHandleInformation NewInfo; + NewInfo.HandledObject = GripInfo.GrippedObject; + NewInfo.GripID = GripInfo.GripID; + + int index = PhysicsGrips.Add(NewInfo); + + return &PhysicsGrips[index]; +} + + +//============================================================================= +void UGripMotionControllerComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, ReplicatedControllerTransform, COND_SkipOwner); + DOREPLIFETIME(UGripMotionControllerComponent, GrippedObjects); + DOREPLIFETIME(UGripMotionControllerComponent, ControllerNetUpdateRate); + DOREPLIFETIME(UGripMotionControllerComponent, bSmoothReplicatedMotion); + DOREPLIFETIME(UGripMotionControllerComponent, bReplicateWithoutTracking); + + + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, LocallyGrippedObjects, COND_SkipOwner); + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, LocalTransactionBuffer, COND_OwnerOnly); +// DOREPLIFETIME(UGripMotionControllerComponent, bReplicateControllerTransform); +} + +/*void UGripMotionControllerComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeLocation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeRotation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeScale3D, false); +}*/ + +void UGripMotionControllerComponent::Server_SendControllerTransform_Implementation(FBPVRComponentPosRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedControllerTransform = NewTransform; + + // Server should no longer call this RPC itself, but if is using non tracked then it will so keeping auth check + if(!bHasAuthority) + OnRep_ReplicatedControllerTransform(); +} + +bool UGripMotionControllerComponent::Server_SendControllerTransform_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void UGripMotionControllerComponent::FGripViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily) +{ + if (!MotionControllerComponent) + { + return; + } + + // Set up the late update state for the controller component + LateUpdate.Setup(MotionControllerComponent->CalcNewComponentToWorld(FTransform()), MotionControllerComponent, false); +} + +void UGripMotionControllerComponent::GetPhysicsVelocities(const FBPActorGripInformation &Grip, FVector &CurAngularVelocity, FVector &CurLinearVelocity) +{ + UPrimitiveComponent * primComp = Grip.GetGrippedComponent();//Grip.Component; + AActor * pActor = Grip.GetGrippedActor(); + + if (!primComp && pActor) + primComp = Cast(pActor->GetRootComponent()); + + if (!primComp) + { + CurAngularVelocity = FVector::ZeroVector; + CurLinearVelocity = FVector::ZeroVector; + return; + } + + CurAngularVelocity = primComp->GetPhysicsAngularVelocityInDegrees(); + CurLinearVelocity = primComp->GetPhysicsLinearVelocity(); +} + +bool UGripMotionControllerComponent::GetPhysicsConstraintForce(const FBPActorGripInformation& Grip, FVector& AngularForce, FVector& LinearForce) +{ + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(Grip.GripID)) + { + if (PhysHandle->HandleData2.IsValid()) + { + FPhysicsInterface::GetForce(PhysHandle->HandleData2, LinearForce, AngularForce); + return true; + } + } + + return false; +} + +void UGripMotionControllerComponent::GetGripMass(const FBPActorGripInformation& Grip, float& Mass) +{ + UPrimitiveComponent* primComp = Grip.GetGrippedComponent();//Grip.Component; + AActor* pActor = Grip.GetGrippedActor(); + + if (!primComp && pActor) + primComp = Cast(pActor->GetRootComponent()); + + if (!primComp || !primComp->IsSimulatingPhysics()) + { + Mass = 0.f; + return; + } + + Mass = primComp->GetMass(); +} + +FTransform UGripMotionControllerComponent::GetGrippedObjectTransform(const FBPActorGripInformation& Grip) +{ + FTransform returnTrans = FTransform::Identity; + + if (!IsValid(Grip.GrippedObject)) + { + return returnTrans; + } + + if (Grip.GripTargetType == EGripTargetType::ActorGrip) + { + if (AActor* GrippedActor = Cast(Grip.GrippedObject)) + { + returnTrans = GrippedActor->GetActorTransform(); + } + } + else + { + if (UPrimitiveComponent* GrippedComp = Cast(Grip.GrippedObject)) + { + returnTrans = GrippedComp->GetComponentTransform(); + } + } + + return returnTrans; +} + +void UGripMotionControllerComponent::GetGripByActor(FBPActorGripInformation &Grip, AActor * ActorToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ActorToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ActorToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ActorToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::GetGripByComponent(FBPActorGripInformation &Grip, UPrimitiveComponent * ComponentToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ComponentToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ComponentToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ComponentToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::GetGripByObject(FBPActorGripInformation &Grip, UObject * ObjectToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ObjectToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ObjectToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +FBPActorGripInformation * UGripMotionControllerComponent::GetGripPtrByID(uint8 IDToLookForGrip) +{ + if (IDToLookForGrip == INVALID_VRGRIP_ID) + { + return nullptr; + } + + FBPActorGripInformation* GripInfo = GrippedObjects.FindByKey(IDToLookForGrip); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(IDToLookForGrip); + + return GripInfo; +} + +void UGripMotionControllerComponent::GetGripByID(FBPActorGripInformation &Grip, uint8 IDToLookForGrip, EBPVRResultSwitch &Result) +{ + if (IDToLookForGrip == INVALID_VRGRIP_ID) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(IDToLookForGrip); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(IDToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripHybridLock(const FBPActorGripInformation& Grip, EBPVRResultSwitch& Result, bool bIsLocked) +{ + int fIndex = GrippedObjects.Find(Grip); + + FBPActorGripInformation* GripInformation = nullptr; + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr) + { + GripInformation->bLockHybridGrip = bIsLocked; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripPaused(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, bool bIsPaused, bool bNoConstraintWhenPaused) +{ + int fIndex = GrippedObjects.Find(Grip); + + FBPActorGripInformation * GripInformation = nullptr; + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr) + { + if (bNoConstraintWhenPaused) + { + if (bIsPaused) + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(*GripInformation)) + { + DestroyPhysicsHandle(*GripInformation); + } + } + else + { + ReCreateGrip(*GripInformation); + } + } + + GripInformation->bIsPaused = bIsPaused; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetPausedTransform(const FBPActorGripInformation &Grip, const FTransform & PausedTransform, bool bTeleport) +{ + + FBPActorGripInformation * GripInformation = nullptr; + + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr && GripInformation->GrippedObject != nullptr) + { + if (bTeleport) + { + FTransform ProxyTrans = PausedTransform; + TeleportMoveGrip_Impl(*GripInformation, true, true, ProxyTrans); + } + else + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(GrippedObjects[fIndex])) + { + UpdatePhysicsHandleTransform(*GripInformation, PausedTransform); + } + else + { + if (GripInformation->GripTargetType == EGripTargetType::ActorGrip) + { + GripInformation->GetGrippedActor()->SetActorTransform(PausedTransform); + } + else + { + GripInformation->GetGrippedComponent()->SetWorldTransform(PausedTransform); + } + } + } + } +} + + + + +void UGripMotionControllerComponent::SetGripCollisionType(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, EGripCollisionType NewGripCollisionType) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].GripCollisionType = NewGripCollisionType; + ReCreateGrip(GrippedObjects[fIndex]); + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].GripCollisionType = NewGripCollisionType; + + if (IsLocallyControlled() && !IsServer() && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + ReCreateGrip(LocallyGrippedObjects[fIndex]); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripLateUpdateSetting(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, EGripLateUpdateSettings NewGripLateUpdateSetting) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].GripLateUpdateSetting = NewGripLateUpdateSetting; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].GripLateUpdateSetting = NewGripLateUpdateSetting; + + if (IsLocallyControlled() && !IsServer() && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripRelativeTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewRelativeTransform + ) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].RelativeTransform = NewRelativeTransform; + if (FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip)) + { + UpdatePhysicsHandle(Grip.GripID, true); + NotifyGripTransformChanged(Grip); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].RelativeTransform = NewRelativeTransform; + if (FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip)) + { + UpdatePhysicsHandle(Grip.GripID, true); + NotifyGripTransformChanged(Grip); + } + + if (IsLocallyControlled() && !IsServer() && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripAdditionTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewAdditionTransform, bool bMakeGripRelative + ) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].AdditionTransform = CreateGripRelativeAdditionTransform(Grip, NewAdditionTransform, bMakeGripRelative); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].AdditionTransform = CreateGripRelativeAdditionTransform(Grip, NewAdditionTransform, bMakeGripRelative); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripStiffnessAndDamping( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + float NewStiffness, float NewDamping, bool bAlsoSetAngularValues, float OptionalAngularStiffness, float OptionalAngularDamping + ) +{ + Result = EBPVRResultSwitch::OnFailed; + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].Stiffness = NewStiffness; + GrippedObjects[fIndex].Damping = NewDamping; + + if (bAlsoSetAngularValues) + { + GrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularStiffness = OptionalAngularStiffness; + GrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularDamping = OptionalAngularDamping; + } + + Result = EBPVRResultSwitch::OnSucceeded; + SetGripConstraintStiffnessAndDamping(&GrippedObjects[fIndex]); + //return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].Stiffness = NewStiffness; + LocallyGrippedObjects[fIndex].Damping = NewDamping; + + if (bAlsoSetAngularValues) + { + LocallyGrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularStiffness = OptionalAngularStiffness; + LocallyGrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularDamping = OptionalAngularDamping; + } + + if (IsLocallyControlled() && !IsServer() && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + SetGripConstraintStiffnessAndDamping(&LocallyGrippedObjects[fIndex]); + // return; + } + } +} + +FTransform UGripMotionControllerComponent::CreateGripRelativeAdditionTransform_BP( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative +) +{ + return CreateGripRelativeAdditionTransform(GripToSample, AdditionTransform, bGripRelative); +} + +bool UGripMotionControllerComponent::GripObject( + UObject * ObjectToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip) +{ + + // Skip if we are traveling + if (IsTravelingOrNullWorld()) + return false; + + if (UPrimitiveComponent * PrimComp = Cast(ObjectToGrip)) + { + return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, OptionalBoneToGripName, GripCollisionType,GripLateUpdateSetting,GripMovementReplicationSetting,GripStiffness,GripDamping, bIsSlotGrip); + } + else if (AActor * Actor = Cast(ObjectToGrip)) + { + return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, OptionalBoneToGripName, GripCollisionType, GripLateUpdateSetting, GripMovementReplicationSetting, GripStiffness, GripDamping, bIsSlotGrip); + } + + return false; +} + +bool UGripMotionControllerComponent::DropObject( + UObject* ObjectToDrop, + uint8 GripIDToDrop, + bool bSimulate, + FVector OptionalAngularVelocity, + FVector OptionalLinearVelocity) +{ + if (IsValid(ObjectToDrop)) + { + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + + if (GripInfo != nullptr && IsValid(GripInfo->GrippedObject)) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + else if (GripIDToDrop != INVALID_VRGRIP_ID) + { + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + + if (GripInfo != nullptr && IsValid(GripInfo->GrippedObject)) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + + return false; +} + +bool UGripMotionControllerComponent::GripObjectByInterface(UObject* ObjectToGrip, const FTransform &WorldOffset, bool bWorldOffsetIsRelative, FName OptionalBoneToGripName, FName OptionalSnapToSocketName, bool bIsSlotGrip) +{ + // Skip if we are traveling + if (IsTravelingOrNullWorld()) + return false; + + if (!IsValid(ObjectToGrip)) + { + return false; + } + + if (UPrimitiveComponent * PrimComp = Cast(ObjectToGrip)) + { + AActor * Owner = PrimComp->GetOwner(); + + if (!IsValid(Owner)) + return false; + + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(PrimComp, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(PrimComp, Stiffness, Damping); + + return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(PrimComp), + IVRGripInterface::Execute_GripMovementReplicationType(PrimComp), + Stiffness, + Damping, + bIsSlotGrip + ); + } + else if (Owner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(Owner, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(Owner, Stiffness, Damping); + + return GripActor(Owner, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Owner), + IVRGripInterface::Execute_GripMovementReplicationType(Owner), + Stiffness, + Damping, + bIsSlotGrip + ); + /*return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Owner), + IVRGripInterface::Execute_GripMovementReplicationType(Owner), + Stiffness, + Damping, + bIsSlotGrip + );*/ + } + else + { + // No interface, no grip + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController GripObjectByInterface was called on an object that doesn't implement the interface and doesn't have a parent that implements the interface!")); + return false; + } + } + else if (AActor * Actor = Cast(ObjectToGrip)) + { + UPrimitiveComponent * root = Cast(Actor->GetRootComponent()); + + if (!IsValid(root)) + return false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(root, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(root, Stiffness, Damping); + + return GripComponent(root, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(root), + IVRGripInterface::Execute_GripMovementReplicationType(root), + Stiffness, + Damping, + bIsSlotGrip + ); + /*return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(root), + IVRGripInterface::Execute_GripMovementReplicationType(root), + Stiffness, + Damping, + bIsSlotGrip + );*/ + } + else if (Actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(Actor, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(Actor, Stiffness, Damping); + + return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Actor), + IVRGripInterface::Execute_GripMovementReplicationType(Actor), + Stiffness, + Damping, + bIsSlotGrip + ); + } + else + { + // No interface, no grip + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController GripObjectByInterface was called on an object that doesn't implement the interface and doesn't have a parent that implements the interface!")); + return false; + } + } + + return false; +} + +bool UGripMotionControllerComponent::DropObjectByInterface(UObject* ObjectToDrop, uint8 GripIDToDrop, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + return DropObjectByInterface_Implementation(ObjectToDrop, GripIDToDrop, OptionalAngularVelocity, OptionalLinearVelocity, false); +} + +bool UGripMotionControllerComponent::DropObjectByInterface_Implementation(UObject* ObjectToDrop, uint8 GripIDToDrop, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity, bool bSkipNotify) +{ + + FBPActorGripInformation * GripInfo = nullptr; + if (IsValid(ObjectToDrop)) + { + GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + } + else if (GripIDToDrop != INVALID_VRGRIP_ID) + { + GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + } + + if (GripInfo == nullptr || !IsValid(GripInfo->GrippedObject)) + { + return false; + } + + if (UPrimitiveComponent * PrimComp = Cast(GripInfo->GrippedObject)) + { + AActor * Owner = PrimComp->GetOwner(); + + if (!Owner) + return false; + + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(PrimComp), OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + //return DropComponent(PrimComp, IVRGripInterface::Execute_SimulateOnDrop(PrimComp), OptionalAngularVelocity, OptionalLinearVelocity); + } + else if (Owner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(Owner), OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + //return DropComponent(PrimComp, IVRGripInterface::Execute_SimulateOnDrop(Owner), OptionalAngularVelocity, OptionalLinearVelocity); + } + else + { + // Allowing for failsafe dropping here. + return DropGrip_Implementation(*GripInfo, true, OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + //return DropComponent(PrimComp, true, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + else if (AActor * Actor = Cast(GripInfo->GrippedObject)) + { + UPrimitiveComponent * root = Cast(Actor->GetRootComponent()); + + if (!IsValid(root)) + return false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(root), OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + } + else if (Actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(Actor), OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + } + else + { + // Failsafe drop here + return DropGrip_Implementation(*GripInfo, true, OptionalAngularVelocity, OptionalLinearVelocity, bSkipNotify); + } + } + + return false; +} + +bool UGripMotionControllerComponent::GripActor( + AActor* ActorToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip) +{ + // Skip if we are traveling + if (IsTravelingOrNullWorld()) + return false; + + bool bIsLocalGrip = (GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep); + + if (!IsServer() && !bIsLocalGrip) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was called on the client side as a replicated grip")); + return false; + } + + if (!ActorToGrip || !IsValid(ActorToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an invalid or pending kill actor")); + return false; + } + + if (GetIsObjectHeld(ActorToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an already gripped actor")); + return false; + } + + UPrimitiveComponent *root = Cast(ActorToGrip->GetRootComponent()); + + if (!root) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip an actor without a UPrimitiveComponent Root")); + return false; // Need a primitive root + } + + // Has to be movable to work + if (root->Mobility != EComponentMobility::Movable && (GripCollisionType != EGripCollisionType::CustomGrip && GripCollisionType != EGripCollisionType::EventsOnly)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip an actor set to static mobility not with a Custom Grip")); + return false; // It is not movable, can't grip it + } + + FBPAdvGripSettings AdvancedGripSettings; + UObject * ObjectToCheck = NULL; // Used if having to calculate the transform + //bool bIgnoreHandRotation = false; + + TArray HoldingControllers; + bool bIsHeld = false; + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(root, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(root, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(root); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(root); + ObjectToCheck = root; + } + else if (ActorToGrip->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(ActorToGrip, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(ActorToGrip, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(ActorToGrip); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(ActorToGrip); + ObjectToCheck = ActorToGrip; + } + + // So that events caused by sweep and the like will trigger correctly + ActorToGrip->AddTickPrerequisiteComponent(this); + + FBPActorGripInformation newActorGrip; + newActorGrip.GripID = GetNextGripID(bIsLocalGrip); + newActorGrip.GripCollisionType = GripCollisionType; + newActorGrip.GrippedObject = ActorToGrip; + if (bHadOriginalSettings) + { + newActorGrip.bOriginalReplicatesMovement = bOriginalReplication; + newActorGrip.bOriginalGravity = bOriginalGravity; + } + else + { + newActorGrip.bOriginalReplicatesMovement = ActorToGrip->IsReplicatingMovement(); + newActorGrip.bOriginalGravity = root->IsGravityEnabled(); + } + newActorGrip.Stiffness = GripStiffness; + newActorGrip.Damping = GripDamping; + newActorGrip.AdvancedGripSettings = AdvancedGripSettings; + newActorGrip.ValueCache.bWasInitiallyRepped = true; // Set this true on authority side so we can skip a function call on tick + newActorGrip.bIsSlotGrip = bIsSlotGrip; + newActorGrip.GrippedBoneName = OptionalBoneToGripName; + newActorGrip.SlotName = OptionalSnapToSocketName; + + // Ignore late update setting if it doesn't make sense with the grip + switch(newActorGrip.GripCollisionType) + { + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + newActorGrip.GripLateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; // Late updates are bad for this grip + }break; + + default: + { + newActorGrip.GripLateUpdateSetting = GripLateUpdateSetting; + }break; + } + + if (GripMovementReplicationSetting == EGripMovementReplicationSettings::KeepOriginalMovement) + { + if (ActorToGrip->IsReplicatingMovement()) + { + newActorGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceServerSideMovement; + } + else + { + newActorGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + } + else + newActorGrip.GripMovementReplicationSetting = GripMovementReplicationSetting; + + newActorGrip.GripTargetType = EGripTargetType::ActorGrip; + + if (OptionalSnapToSocketName.IsValid() && WorldOffset.Equals(FTransform::Identity) && root->DoesSocketExist(OptionalSnapToSocketName)) + { + // I inverse it so that laying out the sockets makes sense + FTransform sockTrans = root->GetSocketTransform(OptionalSnapToSocketName, ERelativeTransformSpace::RTS_Component); + sockTrans.SetScale3D(FVector(1.f) / root->GetComponentScale()); // Prep this so that the inverse works correctly + newActorGrip.RelativeTransform = sockTrans.Inverse(); + newActorGrip.bIsSlotGrip = true; // Set this to a slot grip + + ObjectToCheck = NULL; // Null it back out, socketed grips don't use this + + newActorGrip.SlotName = OptionalSnapToSocketName; + } + else if (bWorldOffsetIsRelative) + { + if (bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) && !bIsSlotGrip) + { + newActorGrip.RelativeTransform = (WorldOffset * this->GetComponentTransform()).GetRelativeTransform(CustomPivotComponent->GetComponentTransform()); + } + else + { + newActorGrip.RelativeTransform = WorldOffset; + } + } + else + { + newActorGrip.RelativeTransform = WorldOffset.GetRelativeTransform(GetPivotTransform()); + } + + if (!bIsLocalGrip) + { + int32 Index = GrippedObjects.Add(newActorGrip); + if (Index != INDEX_NONE) + NotifyGrip(GrippedObjects[Index]); + //NotifyGrip(newActorGrip); + } + else + { + if (!IsLocallyControlled()) + { + LocalTransactionBuffer.Add(newActorGrip); + } + + int32 Index = LocallyGrippedObjects.Add(newActorGrip); + + if (Index != INDEX_NONE) + { + if (!IsLocallyControlled()) + { + if (!HandleGripReplication(LocallyGrippedObjects[Index])) + { + return true; + } + } + else + { + if (!NotifyGrip(LocallyGrippedObjects[Index])) + { + return true; + } + } + + if (bIsLocalGrip && IsLocallyControlled() && !IsServer() && !IsTornOff() && newActorGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + Index = LocallyGrippedObjects.IndexOfByKey(newActorGrip.GripID); + if (Index != INDEX_NONE) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[Index]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + } + } + } + //NotifyGrip(newActorGrip); + + return true; +} + +bool UGripMotionControllerComponent::DropActor(AActor* ActorToDrop, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + if (!ActorToDrop) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid actor")); + return false; + } + + FBPActorGripInformation * GripToDrop = LocallyGrippedObjects.FindByKey(ActorToDrop); + + if(GripToDrop) + return DropGrip_Implementation(*GripToDrop, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side with a replicated grip")); + return false; + } + + GripToDrop = GrippedObjects.FindByKey(ActorToDrop); + if (GripToDrop) + return DropGrip_Implementation(*GripToDrop, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + + return false; +} + +bool UGripMotionControllerComponent::GripComponent( + UPrimitiveComponent* ComponentToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip + ) +{ + // Skip if we are traveling + if (IsTravelingOrNullWorld()) + return false; + + bool bIsLocalGrip = (GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep); + + if (!IsServer() && !bIsLocalGrip) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was called on the client side with a replicating grip")); + return false; + } + + if (!ComponentToGrip || !IsValid(ComponentToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an invalid or pending kill component")); + return false; + } + + if (GetIsObjectHeld(ComponentToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an already gripped component")); + return false; + } + + // Has to be movable to work + if (ComponentToGrip->Mobility != EComponentMobility::Movable && (GripCollisionType != EGripCollisionType::CustomGrip && GripCollisionType != EGripCollisionType::EventsOnly)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip a component set to static mobility not in CustomGrip mode")); + return false; // It is not movable, can't grip it + } + + FBPAdvGripSettings AdvancedGripSettings; + UObject * ObjectToCheck = NULL; + //bool bIgnoreHandRotation = false; + + TArray HoldingControllers; + bool bIsHeld = false; + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (ComponentToGrip->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(ComponentToGrip, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(ComponentToGrip, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(ComponentToGrip); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if(bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation gripInfo; + EBPVRResultSwitch result; + HoldingControllers[0].HoldingController->GetGripByID(gripInfo, HoldingControllers[0].GripID, result); + + if (result != EBPVRResultSwitch::OnFailed) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo.bOriginalGravity; + bOriginalReplication = gripInfo.bOriginalReplicatesMovement; + } + } + } + + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(ComponentToGrip); + ObjectToCheck = ComponentToGrip; + } + + //ComponentToGrip->IgnoreActorWhenMoving(this->GetOwner(), true); + // So that events caused by sweep and the like will trigger correctly + + ComponentToGrip->AddTickPrerequisiteComponent(this); + + FBPActorGripInformation newComponentGrip; + newComponentGrip.GripID = GetNextGripID(bIsLocalGrip); + newComponentGrip.GripCollisionType = GripCollisionType; + newComponentGrip.GrippedObject = ComponentToGrip; + + if (bHadOriginalSettings) + { + newComponentGrip.bOriginalReplicatesMovement = bOriginalReplication; + newComponentGrip.bOriginalGravity = bOriginalGravity; + } + else + { + if (ComponentToGrip->GetOwner()) + newComponentGrip.bOriginalReplicatesMovement = ComponentToGrip->GetOwner()->IsReplicatingMovement(); + + newComponentGrip.bOriginalGravity = ComponentToGrip->IsGravityEnabled(); + } + newComponentGrip.Stiffness = GripStiffness; + newComponentGrip.Damping = GripDamping; + newComponentGrip.AdvancedGripSettings = AdvancedGripSettings; + newComponentGrip.GripTargetType = EGripTargetType::ComponentGrip; + newComponentGrip.ValueCache.bWasInitiallyRepped = true; // Set this true on authority side so we can skip a function call on tick + newComponentGrip.bIsSlotGrip = bIsSlotGrip; + newComponentGrip.GrippedBoneName = OptionalBoneToGripName; + newComponentGrip.SlotName = OptionalSnapToSocketName; + + // Ignore late update setting if it doesn't make sense with the grip + switch (newComponentGrip.GripCollisionType) + { + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + newComponentGrip.GripLateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; // Late updates are bad for this grip + }break; + + default: + { + newComponentGrip.GripLateUpdateSetting = GripLateUpdateSetting; + }break; + } + + + if (GripMovementReplicationSetting == EGripMovementReplicationSettings::KeepOriginalMovement) + { + if (ComponentToGrip->GetOwner()) + { + if (ComponentToGrip->GetOwner()->IsReplicatingMovement()) + { + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceServerSideMovement; + } + else + { + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + } + else + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + else + newComponentGrip.GripMovementReplicationSetting = GripMovementReplicationSetting; + + if (OptionalSnapToSocketName.IsValid() && WorldOffset.Equals(FTransform::Identity) && ComponentToGrip->DoesSocketExist(OptionalSnapToSocketName)) + { + // I inverse it so that laying out the sockets makes sense + FTransform sockTrans = ComponentToGrip->GetSocketTransform(OptionalSnapToSocketName, ERelativeTransformSpace::RTS_Component); + sockTrans.SetScale3D(FVector(1.f) / ComponentToGrip->GetComponentScale()); // Prep this so that the inverse works correctly + newComponentGrip.RelativeTransform = sockTrans.Inverse(); + newComponentGrip.bIsSlotGrip = true; // Set this to a slot grip + + ObjectToCheck = NULL; // Null it out, socketed grips don't use this + } + else if (bWorldOffsetIsRelative) + { + if (bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) && !bIsSlotGrip) + { + newComponentGrip.RelativeTransform = (WorldOffset * this->GetComponentTransform()).GetRelativeTransform(CustomPivotComponent->GetComponentTransform()); + } + else + { + newComponentGrip.RelativeTransform = WorldOffset; + } + } + else + { + newComponentGrip.RelativeTransform = WorldOffset.GetRelativeTransform(GetPivotTransform()); + } + + if (!bIsLocalGrip) + { + int32 Index = GrippedObjects.Add(newComponentGrip); + if (Index != INDEX_NONE) + NotifyGrip(GrippedObjects[Index]); + + //NotifyGrip(newComponentGrip); + } + else + { + if (!IsLocallyControlled()) + { + LocalTransactionBuffer.Add(newComponentGrip); + } + + int32 Index = LocallyGrippedObjects.Add(newComponentGrip); + + if (Index != INDEX_NONE) + { + if (!IsLocallyControlled()) + { + if (!HandleGripReplication(LocallyGrippedObjects[Index])) + { + return true; + } + } + else + { + if (!NotifyGrip(LocallyGrippedObjects[Index])) + { + return true; + } + } + + if (bIsLocalGrip && IsLocallyControlled() && !IsServer() && !IsTornOff() && newComponentGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + Index = LocallyGrippedObjects.IndexOfByKey(newComponentGrip.GripID); + if (Index != INDEX_NONE) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[Index]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + } + } + } + + return true; +} + +bool UGripMotionControllerComponent::DropComponent(UPrimitiveComponent * ComponentToDrop, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + FBPActorGripInformation *GripInfo; + + // First check for it in the local grips + GripInfo = LocallyGrippedObjects.FindByKey(ComponentToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + + // If we aren't the server then fail out + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side for a replicated grip")); + return false; + } + + // Now check in the server auth gripsop) + GripInfo = GrippedObjects.FindByKey(ComponentToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid component")); + return false; + } + + //return false; +} + +bool UGripMotionControllerComponent::DropGrip(const FBPActorGripInformation& Grip, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + return DropGrip_Implementation(Grip, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); +} + +bool UGripMotionControllerComponent::DropGrip_Implementation(const FBPActorGripInformation &Grip, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity, bool bSkipNotify) +{ + int FoundIndex = 0; + bool bIsServer = IsServer(); + bool bWasLocalGrip = false; + if (!LocallyGrippedObjects.Find(Grip, FoundIndex)) // This auto checks if Actor and Component are valid in the == operator + { + if (!bIsServer) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side for a replicated grip")); + return false; + } + + if (!GrippedObjects.Find(Grip, FoundIndex)) // This auto checks if Actor and Component are valid in the == operator + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid drop")); + return false; + } + + bWasLocalGrip = false; + } + else + bWasLocalGrip = true; + + if (bWasLocalGrip && bIsServer) + { + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; i--) + { + if (LocalTransactionBuffer[i].GripID == Grip.GripID) + LocalTransactionBuffer.RemoveAt(i); + } + } + + UPrimitiveComponent * PrimComp = nullptr; + + AActor * pActor = nullptr; + if (bWasLocalGrip) + { + PrimComp = LocallyGrippedObjects[FoundIndex].GetGrippedComponent(); + pActor = LocallyGrippedObjects[FoundIndex].GetGrippedActor(); + } + else + { + PrimComp = GrippedObjects[FoundIndex].GetGrippedComponent(); + pActor = GrippedObjects[FoundIndex].GetGrippedActor(); + } + + if (!PrimComp && pActor) + PrimComp = Cast(pActor->GetRootComponent()); + + if(!PrimComp) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid drop or CleanUpBadGrip wascalled")); + //return false; + } + else + { + if (bSimulate && (!OptionalLinearVelocity.IsNearlyZero() || !OptionalAngularVelocity.IsNearlyZero())) + { + if (Grip.GripCollisionType != EGripCollisionType::EventsOnly) + { + // Need to set simulation in all of these cases, including if it isn't the root component (simulation isn't replicated on non roots) + if (!Grip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !Grip.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + if (PrimComp->IsSimulatingPhysics() != bSimulate) + { + PrimComp->SetSimulatePhysics(bSimulate); + } + } + } + + // Had to move in front of deletion to properly set velocity + if (PrimComp->IsSimulatingPhysics()) + { + PrimComp->SetPhysicsLinearVelocity(OptionalLinearVelocity); + PrimComp->SetPhysicsAngularVelocityInDegrees(OptionalAngularVelocity); + } + } + } + + if (bWasLocalGrip) + { + // Store out a local copy so we can't get funky issues with the engine dropping the grip out from under us + FBPActorGripInformation GripInfo = LocallyGrippedObjects[FoundIndex]; + + if (IsLocallyControlled() && !IsServer()) //GetNetMode() == ENetMode::NM_Client) + { + if (!IsTornOff()) + { + FTransform_NetQuantize TransformAtDrop = FTransform::Identity; + + switch (GripInfo.GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor * GrippedActor = GripInfo.GetGrippedActor()) + { + TransformAtDrop = GrippedActor->GetActorTransform(); + } + }; break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent * GrippedPrim = GripInfo.GetGrippedComponent()) + { + TransformAtDrop = GrippedPrim->GetComponentTransform(); + } + }break; + default:break; + } + + if(!bSkipNotify) + Server_NotifyLocalGripRemoved(GripInfo.GripID, TransformAtDrop, OptionalAngularVelocity, OptionalLinearVelocity); + } + + // Double check we didn't lose the grip (seems to be possible in UE5 from the Server RPC above being ran on the client) + if (LocallyGrippedObjects.Num() > 0 && LocallyGrippedObjects.Find(GripInfo, FoundIndex)) + { + // Have to call this ourselves + Drop_Implementation(GripInfo, bSimulate); + } + } + else // Server notifyDrop it + { + NotifyDrop(GripInfo, bSimulate); + } + } + else + NotifyDrop(GrippedObjects[FoundIndex], bSimulate); + + //GrippedObjects.RemoveAt(FoundIndex); + return true; +} + +bool UGripMotionControllerComponent::DropAndSocketObject(const FTransform_NetQuantize & RelativeTransformToParent, UObject * ObjectToDrop, uint8 GripIDToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, bool bWeldBodies) +{ + if (!SocketingParent) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid socketing parent")); + return false; + } + + if (!ObjectToDrop && GripIDToDrop == INVALID_VRGRIP_ID) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid object")); + return false; + } + + bool bWasLocalGrip = false; + FBPActorGripInformation * GripInfo = nullptr; + + if (ObjectToDrop) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + else if (GripIDToDrop != INVALID_VRGRIP_ID) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + + if(GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = true; + } + else + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was called on the client side for a replicated grip")); + return false; + } + + if(ObjectToDrop) + GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + else if(GripIDToDrop != INVALID_VRGRIP_ID) + GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + + if(GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = false; + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop")); + return false; + } + } + + if(GripInfo) + return DropAndSocketGrip_Implementation(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + + return false; +} + +bool UGripMotionControllerComponent::DropAndSocketGrip(const FBPActorGripInformation& GripToDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies) +{ + return DropAndSocketGrip_Implementation(GripToDrop, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); +} + +bool UGripMotionControllerComponent::DropAndSocketGrip_Implementation(const FBPActorGripInformation & GripToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies, bool bSkipServerNotify) +{ + if (!SocketingParent || !IsValid(SocketingParent)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid socketing parent")); + return false; + } + + bool bWasLocalGrip = false; + FBPActorGripInformation * GripInfo = nullptr; + + GripInfo = LocallyGrippedObjects.FindByKey(GripToDrop); + if (GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = true; + } + else + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was called on the client side for a replicated grip")); + return false; + } + + GripInfo = GrippedObjects.FindByKey(GripToDrop); + + if (GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = false; + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop")); + return false; + } + } + + UPrimitiveComponent * PrimComp = nullptr; + + AActor * pActor = nullptr; + + PrimComp = GripInfo->GetGrippedComponent(); + pActor = GripInfo->GetGrippedActor(); + + if (!PrimComp && pActor) + PrimComp = Cast(pActor->GetRootComponent()); + + if (!PrimComp) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop or CleanUpBadGrip wascalled")); + //return false; + } + + UObject * GrippedObject = GripInfo->GrippedObject; + + if (!GrippedObject || !IsValid(GrippedObject)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid or pending kill gripped object")); + return false; + } + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(*GripInfo, PhysicsHandleIndex); + + if (bWasLocalGrip) + { + if (IsLocallyControlled() && !IsServer()) + { + if (!IsTornOff() && !bSkipServerNotify) + { + Server_NotifyDropAndSocketGrip(GripInfo->GripID, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + OnSocketingObject.Broadcast(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + + // Have to call this ourselves + DropAndSocket_Implementation(*GripInfo); + } + else // Server notifyDrop it + { + //Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + NotifyDropAndSocket(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + } + else + { + //Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + NotifyDropAndSocket(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + //GrippedObjects.RemoveAt(FoundIndex); + return true; +} + +void UGripMotionControllerComponent::SetSocketTransform(UObject* ObjectToSocket, /*USceneComponent * SocketingParent,*/ const FTransform_NetQuantize RelativeTransformToParent/*, FName OptionalSocketName, bool bWeldBodies*/) +{ + if (ObjectsWaitingForSocketUpdate.RemoveSingle(ObjectToSocket) < 1) + { + // I know that technically it should never happen that the pointers get reset with a uproperty + // But does it really hurt to add this pathway anyway? + for (int i = ObjectsWaitingForSocketUpdate.Num() - 1; i >= 0; --i) + { + if (ObjectsWaitingForSocketUpdate[i] == nullptr) + ObjectsWaitingForSocketUpdate.RemoveAt(i); + } + + return; + } + + if (!ObjectToSocket || !IsValid(ObjectToSocket)) + return; + + /*FAttachmentTransformRules TransformRule = FAttachmentTransformRules::KeepWorldTransform;//KeepWorldTransform; + TransformRule.bWeldSimulatedBodies = bWeldBodies;*/ + + if (UPrimitiveComponent * root = Cast(ObjectToSocket)) + { + //root->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + //root->SetRelativeTransform(RelativeTransformToParent); + + if(root->GetAttachParent()) + root->SetRelativeTransform(RelativeTransformToParent); + } + else if (AActor * pActor = Cast(ObjectToSocket)) + { + //pActor->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + //pActor->SetActorRelativeTransform(RelativeTransformToParent); + + if(pActor->GetAttachParentActor()) + pActor->SetActorRelativeTransform(RelativeTransformToParent); + } +} + + +bool UGripMotionControllerComponent::Server_NotifyDropAndSocketGrip_Validate(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyDropAndSocketGrip_Implementation(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + + GetGripByID(FoundGrip, GripID, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(FoundGrip, PhysicsHandleIndex); + + if (FoundGrip.GrippedObject) + { + OnSocketingObject.Broadcast(FoundGrip, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(FoundGrip.GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + if (!DropAndSocketGrip_Implementation(FoundGrip, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies, true)) + { + DropGrip_Implementation(FoundGrip, false, FVector::ZeroVector, FVector::ZeroVector, true); + } + +} + +void UGripMotionControllerComponent::Socket_Implementation(UObject * ObjectToSocket, bool bWasSimulating, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + // Check for valid objects + if (!SocketingParent || !SocketingParent->IsValidLowLevelFast() || !ObjectToSocket || !ObjectToSocket->IsValidLowLevelFast()) + { + if (!SocketingParent || !SocketingParent->IsValidLowLevelFast()) + { + UE_LOG(LogVRMotionController, Error, TEXT("VRGripMotionController Socket_Implementation was called with an invalid Socketing Parent object")); + } + else + { + UE_LOG(LogVRMotionController, Error, TEXT("VRGripMotionController Socket_Implementation was called with an invalid Object to Socket")); + } + return; + } + + FAttachmentTransformRules TransformRule = FAttachmentTransformRules::KeepWorldTransform;//KeepWorldTransform; + TransformRule.bWeldSimulatedBodies = bWeldBodies; + + //UPrimitiveComponent * ParentPrim = Cast(SocketingParent); + + if (UPrimitiveComponent * root = Cast(ObjectToSocket)) + { + if (FBodyInstance* rBodyInstance = root->GetBodyInstance()) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + // Stop simulation for socketing + if (bWasSimulating || root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + bWasSimulating = true; + } + + root->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + root->SetRelativeTransform(RelativeTransformToParent); + } + else if (AActor * pActor = Cast(ObjectToSocket)) + { + + if (UPrimitiveComponent * rootComp = Cast(pActor->GetRootComponent())) + { + if (FBodyInstance* rBodyInstance = rootComp->GetBodyInstance()) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + if (bWasSimulating || rootComp->IsSimulatingPhysics()) + { + // Stop simulation for socketing + rootComp->SetSimulatePhysics(false); + bWasSimulating = true; + } + } + + pActor->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + pActor->SetActorRelativeTransform(RelativeTransformToParent); + + //if (!bRetainOwnership) + //pActor->SetOwner(nullptr); + } + + // It had a physics handle or was simulating, I need to delay a tick and set the transform to ensure it skips a race condition + // I may need to consider running the entire attachment in here instead in the future + if (bWasSimulating) + { + ObjectsWaitingForSocketUpdate.Add(ObjectToSocket); + GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UGripMotionControllerComponent::SetSocketTransform, ObjectToSocket, /*SocketingParent, */RelativeTransformToParent/*, OptionalSocketName, bWeldBodies*/)); + } +} + +void UGripMotionControllerComponent::NotifyDropAndSocket_Implementation(const FBPActorGripInformation &NewDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies) +{ + // Don't do this if we are the owning player on a local grip, there is no filter for multicast to not send to owner + if ((NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && + IsLocallyControlled() && + !IsServer()) + { + + // If we still have the grip then the server is asking us to drop it even though it is locally controlled + if (FBPActorGripInformation * GripInfo = GetGripPtrByID(NewDrop.GripID)) + { + DropAndSocketGrip_Implementation(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies, true); + } + return; + } + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(NewDrop, PhysicsHandleIndex); + + if (NewDrop.GrippedObject) + { + OnSocketingObject.Broadcast(NewDrop, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(NewDrop.GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + DropAndSocket_Implementation(NewDrop); +} + +void UGripMotionControllerComponent::DropAndSocket_Implementation(const FBPActorGripInformation &NewDrop) +{ + UGripMotionControllerComponent * HoldingController = nullptr; + bool bIsHeld = false; + + DestroyPhysicsHandle(NewDrop); + + bool bHadGripAuthority = HasGripAuthority(NewDrop); + + UPrimitiveComponent *root = NULL; + AActor * pActor = NULL; + + switch (NewDrop.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewDrop.GetGrippedActor(); + + if (pActor) + { + root = Cast(pActor->GetRootComponent()); + + pActor->RemoveTickPrerequisiteComponent(this); + //this->IgnoreActorWhenMoving(pActor, false); + + if (APawn* OwningPawn = Cast(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + + // Clearing owner out here + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + // #TODO: Add the removal back in? + //pActor->SetOwner(nullptr); + } + + if (root) + { + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + // Attachment already handles both of these + //root->UpdateComponentToWorld(); // This fixes the late update offset + //root->SetSimulatePhysics(false); + + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + + // Stop Physics sim for socketing + root->SetSimulatePhysics(false); + } + + if (IsServer()) //&& !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewDrop.GripID, false); + + if(NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(pActor, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, true); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(pActor, this, NewDrop, true); + if (IVRGripInterface* GripInterface = Cast(pActor)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, true); + } + + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewDrop.GetGrippedComponent(); + if (root) + { + pActor = root->GetOwner(); + + root->RemoveTickPrerequisiteComponent(this); + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + // Attachment already handles both of these + //root->UpdateComponentToWorld(); + //root->SetSimulatePhysics(false); + + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + + // Stop Physics sim for socketing + root->SetSimulatePhysics(false); + + if (pActor) + { + if (IsServer() && root == pActor->GetRootComponent()) //&& !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(pActor, this, NewDrop, true); + } + } + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(root, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(root, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, true); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(root, this, NewDrop, true); + if (IVRGripInterface* GripInterface = Cast(root)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, true); + } + } + + // Call on child grip release on attached parent component + if (root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(root->GetAttachParent(), this, NewDrop, true); + } + } + }break; + } + + // Copy over the information instead of working with a reference for the OnDroppedBroadcast + FBPActorGripInformation DropBroadcastData = NewDrop; + + int fIndex = 0; + if (LocallyGrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || IsServer()) + { + LocallyGrippedObjects.RemoveAt(fIndex); + } + else + { + LocallyGrippedObjects[fIndex].bIsPendingKill = true; + LocallyGrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + else + { + fIndex = 0; + if (GrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || IsServer()) + { + GrippedObjects.RemoveAt(fIndex); + } + else + { + GrippedObjects[fIndex].bIsPendingKill = true; + GrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + } + + // Broadcast a new drop + OnDroppedObject.Broadcast(DropBroadcastData, true); +} + + +// No longer an RPC, now is called from RepNotify so that joining clients also correctly set up grips +bool UGripMotionControllerComponent::NotifyGrip(FBPActorGripInformation &NewGrip, bool bIsReInit) +{ + UPrimitiveComponent *root = NULL; + AActor *pActor = NULL; + + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + if (!NewGrip.GrippedObject || !NewGrip.GrippedObject->IsValidLowLevelFast()) + return false; + + if (!NewGrip.AdvancedGripSettings.bDisallowLerping && !bIsReInit && NewGrip.GripCollisionType != EGripCollisionType::EventsOnly && NewGrip.GripCollisionType != EGripCollisionType::CustomGrip) + { + // Init lerping + InitializeLerpToHand(NewGrip); + } + + switch (NewGrip.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewGrip.GetGrippedActor(); + + if (pActor) + { + root = Cast(pActor->GetRootComponent()); + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (APawn* OwningPawn = Cast(GetOwner())) + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + OwningPawn->MoveIgnoreActorAdd(pActor); + } + + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + if (NewGrip.AdvancedGripSettings.bSetOwnerOnGrip) + { + if (IsServer()) + { + pActor->SetOwner(OwningPawn); + } + } + } + + if (!bIsReInit && bActorHasInterface) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewGrip.GripID, true); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnGrip(this, NewGrip); + } + } + } + + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnGrip(pActor, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + // Now check for c++ specific implementation and throw the native event if we need too + if (IVRGripInterface* GripInterface = Cast(pActor)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, true, NewGrip, false); + + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + } + + if (root) + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + // Have to turn off gravity locally + if ((NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(false); + } + //root->IgnoreActorWhenMoving(this->GetOwner(), true); + } + + + } + else + return false; + + if (bActorHasInterface && !EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bProjectNonSimulatingGrips) + { + RegisterEndPhysicsTick(true); + } + else + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(pActor); + + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + RegisterEndPhysicsTick(true); + } + } + } + + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewGrip.GetGrippedComponent(); + + if (root) + { + pActor = root->GetOwner(); + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (!bIsReInit && bRootHasInterface) + { + IVRGripInterface::Execute_SetHeld(root, this, NewGrip.GripID, true); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnGrip(this, NewGrip); + } + } + } + + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnGrip(root, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + // Now throw the native event if it implements the native interface + if (IVRGripInterface* GripInterface = Cast(root)) + { + //GripInterface->Execute_OnGrip(root, this, NewGrip); + GripInterface->Native_NotifyThrowGripDelegates(this, true, NewGrip, false); + + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + } + + } + + if (pActor) + { + /*if (APawn* OwningPawn = Cast(GetOwner())) + { + OwningPawn->MoveIgnoreActorAdd(root->GetOwner()); + }*/ + + if (!bIsReInit && bActorHasInterface) + { + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnChildGrip(pActor, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + } + + // Call OnChildGrip for attached grip parent + if (!bIsReInit && root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnChildGrip(root->GetAttachParent(), this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(false); + } + + //root->IgnoreActorWhenMoving(this->GetOwner(), true); + } + else + return false; + + if (bRootHasInterface && !EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bProjectNonSimulatingGrips) + { + RegisterEndPhysicsTick(true); + } + else + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(root); + + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + RegisterEndPhysicsTick(true); + } + } + } + + }break; + } + + switch (NewGrip.GripMovementReplicationSetting) + { + case EGripMovementReplicationSettings::ForceClientSideMovement: + case EGripMovementReplicationSettings::ClientSide_Authoritive: + case EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep: + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() && pActor && ((NewGrip.GripTargetType == EGripTargetType::ActorGrip) || (root && root == pActor->GetRootComponent()))) + { + pActor->SetReplicateMovement(false); + } + if (root) + { + // #TODO: This is a hack until Epic fixes their new physics replication code + // It forces the replication target to null on grip if we aren't repping movement. + + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + FBodyInstance* BI = root->GetBodyInstance(NewGrip.GrippedBoneName); + if (BI && BI->IsInstanceSimulatingPhysics()) + { + PhysicsReplication->RemoveReplicatedTarget(root); + //PhysicsReplication->SetReplicatedTarget(this, BoneName, UpdatedState); + } + } + } + } + } + } + + }break; + + case EGripMovementReplicationSettings::ForceServerSideMovement: + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() && pActor && ((NewGrip.GripTargetType == EGripTargetType::ActorGrip) || (root && root == pActor->GetRootComponent()))) + { + pActor->SetReplicateMovement(true); + } + } + }break; + + case EGripMovementReplicationSettings::KeepOriginalMovement: + default: + {}break; + } + + bool bHasMovementAuthority = HasGripMovementAuthority(NewGrip); + + switch (NewGrip.GripCollisionType) + { + case EGripCollisionType::InteractiveCollisionWithPhysics: + case EGripCollisionType::LockedConstraint: + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + if (bHasMovementAuthority) + { + SetUpPhysicsHandle(NewGrip); + } + } break; + + + case EGripCollisionType::InteractiveHybridCollisionWithPhysics: + { + if (bHasMovementAuthority) + { + SetUpPhysicsHandle(NewGrip); + } + } break; + + // Skip collision intersects with these types, they dont need it + case EGripCollisionType::EventsOnly: + case EGripCollisionType::CustomGrip: + { + // Should have never been turning off physics here, simulating is a valid custom grip state + //if (root) + //root->SetSimulatePhysics(false); + + } break; + + case EGripCollisionType::AttachmentGrip: + { + if (root) + root->SetSimulatePhysics(false); + + // Move it to the correct location automatically + if (bHasMovementAuthority) + { + if (!NewGrip.bIsLerping) + { + TeleportMoveGrip(NewGrip); + } + } + + if (bHasMovementAuthority || IsServer()) + { + FName BoneName = IsValid(CustomPivotComponent) ? CustomPivotComponentSocketName : NAME_None; + root->AttachToComponent(IsValid(CustomPivotComponent) ? CustomPivotComponent.Get() : this, FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), BoneName); + } + + }break; + + case EGripCollisionType::PhysicsOnly: + case EGripCollisionType::SweepWithPhysics: + case EGripCollisionType::InteractiveHybridCollisionWithSweep: + case EGripCollisionType::InteractiveCollisionWithSweep: + default: + { + + if (root) + { + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + // #TODO Remove the ELSE below when chaos is fixed for body welding without simulation + else + { + root->SetSimulatePhysics(true); // Forces it to weld children + root->SetSimulatePhysics(false); // Stop it + } + + if(root->GetAttachParent()) + { + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } + } + + // Move it to the correct location automatically + if (bHasMovementAuthority) + { + if (!NewGrip.bIsLerping) + { + TeleportMoveGrip(NewGrip); + } + } + } break; + + } + + if (!bIsReInit) + { + // Broadcast a new grip + OnGrippedObject.Broadcast(NewGrip); + if (!LocallyGrippedObjects.Contains(NewGrip.GripID) && !GrippedObjects.Contains(NewGrip.GripID)) + { + return false; + } + } + + return true; +} + +void UGripMotionControllerComponent::InitializeLerpToHand(FBPActorGripInformation & GripInformation) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + if (!VRSettings.bUseGlobalLerpToHand || VRSettings.LerpDuration <= 0.f) + return; + + if (VRSettings.bSkipLerpToHandIfHeld && GripInformation.GrippedObject && GripInformation.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bIsHeld = false; + TArray HoldingControllers; + // Check if a different controller is holding it + IVRGripInterface::Execute_IsHeld(GripInformation.GrippedObject, HoldingControllers, bIsHeld); + + if (HoldingControllers.Num() > 0) + { + for (FBPGripPair& ControllerPair : HoldingControllers) + { + if (ControllerPair.HoldingController && ControllerPair.HoldingController != this) + { + FBPActorGripInformation Grip; + EBPVRResultSwitch Result; + ControllerPair.HoldingController->GetGripByID(Grip, ControllerPair.GripID, Result); + + if (Result != EBPVRResultSwitch::OnFailed) + { + if (Grip.IsValid()) + { + // We are skipping lerping now + GripInformation.bIsLerping = false; + return; + } + } + } + } + } + } + + EBPVRResultSwitch Result; + TSubclassOf Class = UGS_LerpToHand::StaticClass(); + UVRGripScriptBase::GetGripScriptByClass(GripInformation.GrippedObject, Class, Result); + if (Result == EBPVRResultSwitch::OnSucceeded) + { + return; + } + + if (USceneComponent* PrimParent = Cast(GripInformation.GrippedObject)) + { + if (GripInformation.GrippedBoneName != NAME_None) + { + GripInformation.OnGripTransform = PrimParent->GetSocketTransform(GripInformation.GrippedBoneName); + } + else + { + GripInformation.OnGripTransform = PrimParent->GetComponentTransform(); + } + } + else if (AActor* ParentActor = Cast(GripInformation.GrippedObject)) + { + GripInformation.OnGripTransform = ParentActor->GetActorTransform(); + } + + FTransform TargetTransform = GripInformation.RelativeTransform * this->GetPivotTransform(); + float Distance = FVector::Dist(GripInformation.OnGripTransform.GetLocation(), TargetTransform.GetLocation()); + if (VRSettings.MinDistanceForLerp > 0.0f && Distance < VRSettings.MinDistanceForLerp) + { + // Don't init + GripInformation.bIsLerping = false; + //OnLerpToHandFinished.Broadcast(GripInformation); + return; + } + else + { + float LerpScaler = 1.0f; + float DistanceToSpeed = Distance / VRSettings.LerpDuration; + if (DistanceToSpeed < VRSettings.MinSpeedForLerp) + { + LerpScaler = VRSettings.MinSpeedForLerp / DistanceToSpeed; + } + else if (VRSettings.MaxSpeedForLerp > 0.f && DistanceToSpeed > VRSettings.MaxSpeedForLerp) + { + LerpScaler = VRSettings.MaxSpeedForLerp / DistanceToSpeed; + } + else + { + LerpScaler = 1.0f; + } + + // Get the modified lerp speed + GripInformation.LerpSpeed = ((1.f / VRSettings.LerpDuration) * LerpScaler); + GripInformation.bIsLerping = true; + GripInformation.CurrentLerpTime = 0.0f; + } + + GripInformation.CurrentLerpTime = 0.0f; +} + +void UGripMotionControllerComponent::HandleGlobalLerpToHand(FBPActorGripInformation& GripInformation, FTransform& WorldTransform, float DeltaTime) +{ + UVRGlobalSettings* VRSettings = GetMutableDefault(); + + if (!VRSettings->bUseGlobalLerpToHand || !GripInformation.bIsLerping) + return; + + EBPVRResultSwitch Result; + TSubclassOf Class = UGS_LerpToHand::StaticClass(); + UVRGripScriptBase * LerpScript = UVRGripScriptBase::GetGripScriptByClass(GripInformation.GrippedObject, Class, Result); + if (Result == EBPVRResultSwitch::OnSucceeded && LerpScript && LerpScript->IsScriptActive()) + { + return; + } + + if (VRSettings->LerpDuration <= 0.f) + { + GripInformation.bIsLerping = false; + GripInformation.CurrentLerpTime = 0.f; + OnLerpToHandFinished.Broadcast(GripInformation); + return; + } + + FTransform NA = GripInformation.OnGripTransform;//root->GetComponentTransform(); + float Alpha = 0.0f; + + GripInformation.CurrentLerpTime += DeltaTime * GripInformation.LerpSpeed; + float OrigAlpha = FMath::Clamp(GripInformation.CurrentLerpTime, 0.f, 1.0f); + Alpha = OrigAlpha; + + if (VRSettings->bUseCurve) + { + if (FRichCurve* richCurve = VRSettings->OptionalCurveToFollow.GetRichCurve()) + { + /*if (CurrentLerpTime > richCurve->GetLastKey().Time) + { + // Stop lerping + OnLerpToHandFinished.Broadcast(); + CurrentLerpTime = 0.0f; + bIsActive = false; + return true; + } + else*/ + { + Alpha = FMath::Clamp(richCurve->Eval(Alpha), 0.f, 1.f); + //CurrentLerpTime += DeltaTime; + } + } + } + + FTransform NB = WorldTransform; + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + if (VRSettings->LerpInterpolationMode == EVRLerpInterpolationMode::QuatInterp) + { + WorldTransform.Blend(NA, NB, Alpha); + } + + // Euler Angle interpolation + else if (VRSettings->LerpInterpolationMode == EVRLerpInterpolationMode::EulerInterp) + { + WorldTransform.SetTranslation(FMath::Lerp(NA.GetTranslation(), NB.GetTranslation(), Alpha)); + WorldTransform.SetScale3D(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + + FRotator A = NA.Rotator(); + FRotator B = NB.Rotator(); + WorldTransform.SetRotation(FQuat(A + (Alpha * (B - A)))); + } + // Dual quaternion interpolation + else + { + if ((NB.GetRotation() | NA.GetRotation()) < 0.0f) + { + NB.SetRotation(NB.GetRotation() * -1.0f); + } + WorldTransform = (FDualQuat(NA) * (1 - Alpha) + FDualQuat(NB) * Alpha).Normalized().AsFTransform(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + } + + // Turn it off if we need to + if (OrigAlpha >= 1.0f) + { + GripInformation.CurrentLerpTime = 0.0f; + GripInformation.bIsLerping = false; + + if (bConstrainToPivot) + { + DestroyPhysicsHandle(GripInformation, false); + SetUpPhysicsHandle(GripInformation); + } + + + OnLerpToHandFinished.Broadcast(GripInformation); + } +} + +void UGripMotionControllerComponent::CancelGlobalLerpToHand(uint8 GripID) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + GripToUse->bIsLerping = false; + + if (bConstrainToPivot) + { + DestroyPhysicsHandle(*GripToUse, false); + SetUpPhysicsHandle(*GripToUse); + } + + GripToUse->CurrentLerpTime = 0.0f; + OnLerpToHandFinished.Broadcast(*GripToUse); + } + } +} + +void UGripMotionControllerComponent::NotifyDrop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate) +{ + // Don't do this if we are the owning player on a local grip, there is no filter for multicast to not send to owner + if ((NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && + IsLocallyControlled() && + !IsServer()) + { + // If we still have the grip then the server is asking us to drop it even though it is locally controlled + if (FBPActorGripInformation * GripInfo = GetGripPtrByID(NewDrop.GripID)) + { + DropGrip_Implementation(*GripInfo, bSimulate, FVector::ZeroVector, FVector::ZeroVector, true); + } + + return; + } + + Drop_Implementation(NewDrop, bSimulate); +} + +void UGripMotionControllerComponent::Drop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate) +{ + bool bSkipFullDrop = false; + bool bHadAnotherSelfGrip = false; + TArray HoldingControllers; + bool bIsHeld = false; + + // Check if a different controller is holding it + if(NewDrop.GrippedObject && NewDrop.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + IVRGripInterface::Execute_IsHeld(NewDrop.GrippedObject, HoldingControllers, bIsHeld); + + if (bIsHeld && (!HoldingControllers.Contains(this) || HoldingControllers.Num() > 1)) + { + // Skip the full drop if held + bSkipFullDrop = true; + } + else // Now check for this same hand with duplicate grips on this object + { + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GrippedObject == NewDrop.GrippedObject && LocallyGrippedObjects[i].GripID != NewDrop.GripID) + { + bSkipFullDrop = true; + bHadAnotherSelfGrip = true; + } + } + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GrippedObject == NewDrop.GrippedObject && GrippedObjects[i].GripID != NewDrop.GripID) + { + bSkipFullDrop = true; + bHadAnotherSelfGrip = true; + } + } + } + + DestroyPhysicsHandle(NewDrop, bHadAnotherSelfGrip); + + bool bHadGripAuthority = HasGripAuthority(NewDrop); + + UPrimitiveComponent *root = NULL; + AActor * pActor = NULL; + + switch (NewDrop.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewDrop.GetGrippedActor(); + + if (pActor) + { + root = Cast(pActor->GetRootComponent()); + + if (!bSkipFullDrop) + { + + pActor->RemoveTickPrerequisiteComponent(this); + //this->IgnoreActorWhenMoving(pActor, false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (APawn * OwningPawn = Cast(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + + // Clearing owner out here + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + // #TODO: Add the removal back in? + //pActor->SetOwner(nullptr); + } + } + + if (root) + { + + if (NewDrop.GripCollisionType == EGripCollisionType::AttachmentGrip && (HasGripAuthority(NewDrop) || IsServer())) + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() || bHadGripAuthority || !NewDrop.bOriginalReplicatesMovement || !pActor->GetIsReplicated()) + { + if (!NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewDrop.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + if (root->IsSimulatingPhysics() != bSimulate) + { + root->SetSimulatePhysics(bSimulate); + } + + if (bSimulate) + root->WakeAllRigidBodies(); + } + } + + root->UpdateComponentToWorld(); // This fixes the late update offset + } + + /*if (NewDrop.GrippedBoneName == NAME_None) + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + else + { + USkeletalMeshComponent * skele = Cast(root); + if (skele) + { + skele->SetAllBodiesBelowSimulatePhysics(NewDrop.GrippedBoneName, bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + } + else + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + }*/ + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + } + } + } + + if (IsServer() && !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(pActor, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, false); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(pActor, this, NewDrop, false); + if (IVRGripInterface* GripInterface = Cast(pActor)) + { + //GripInterface->Execute_OnGripRelease(pActor, this, NewDrop, false); + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, false); + } + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewDrop.GetGrippedComponent(); + if (root) + { + pActor = root->GetOwner(); + + if (!bSkipFullDrop) + { + root->RemoveTickPrerequisiteComponent(this); + + /*if (APawn* OwningPawn = Cast(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + }*/ + + if (NewDrop.GripCollisionType == EGripCollisionType::AttachmentGrip && (HasGripAuthority(NewDrop) || IsServer())) + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + // Need to set simulation in all of these cases, including if it isn't the root component (simulation isn't replicated on non roots) + if (IsServer() || bHadGripAuthority || !NewDrop.bOriginalReplicatesMovement || (pActor && (pActor->GetRootComponent() != root || !pActor->GetIsReplicated()))) + { + if (!NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewDrop.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + if (root->IsSimulatingPhysics() != bSimulate) + { + root->SetSimulatePhysics(bSimulate); + } + + if (bSimulate) + root->WakeAllRigidBodies(); + } + } + + root->UpdateComponentToWorld(); // This fixes the late update offset + } + /*if (NewDrop.GrippedBoneName == NAME_None) + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + else + { + USkeletalMeshComponent * skele = Cast(root); + if (skele) + { + skele->SetAllBodiesBelowSimulatePhysics(NewDrop.GrippedBoneName, bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + } + else + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + }*/ + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + } + } + + if (pActor) + { + if (IsServer() && root == pActor->GetRootComponent() && !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(pActor, this, NewDrop, false); + } + + } + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(root, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(root, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, false); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(root, this, NewDrop, false); + if (IVRGripInterface* GripInterface = Cast(root)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, false); + } + + } + + // Call on child grip release on attached parent component + if (root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(root->GetAttachParent(), this, NewDrop, false); + } + } + }break; + } + + + switch (NewDrop.GripMovementReplicationSetting) + { + case EGripMovementReplicationSettings::ForceClientSideMovement: + case EGripMovementReplicationSettings::ClientSide_Authoritive: + case EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep: + { + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (root) + { + // #TODO: This is a hack until Epic fixes their new physics replication code + // It forces the replication target to null on grip if we aren't repping movement. + if (UWorld * World = GetWorld()) + { + if (FPhysScene * PhysScene = World->GetPhysicsScene()) + { + if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + FBodyInstance* BI = root->GetBodyInstance(NewDrop.GrippedBoneName); + if (BI && BI->IsInstanceSimulatingPhysics()) + { + PhysicsReplication->RemoveReplicatedTarget(root); + //PhysicsReplication->SetReplicatedTarget(this, BoneName, UpdatedState); + } + } + } + } + } + } + + }break; + + }; + + + + + // Copy over the information instead of working with a reference for the OnDroppedBroadcast + FBPActorGripInformation DropBroadcastData = NewDrop; + + int fIndex = 0; + if (LocallyGrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || IsServer()) + { + LocallyGrippedObjects.RemoveAt(fIndex); + } + else + { + LocallyGrippedObjects[fIndex].bIsPendingKill = true; + LocallyGrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + else + { + fIndex = 0; + if (GrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || IsServer()) + { + GrippedObjects.RemoveAt(fIndex); + } + else + { + GrippedObjects[fIndex].bIsPendingKill = true; + GrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + } + + // Broadcast a new drop + OnDroppedObject.Broadcast(DropBroadcastData, false); + + + // Now check if we should turn off any post physics ticking + if (EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + bool bNeedsPhysicsTick = false; + + if (LocallyGrippedObjects.Num() > 0 || GrippedObjects.Num() > 0) + { + if (bProjectNonSimulatingGrips) + { + bNeedsPhysicsTick = true; + } + else + { + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (IsValid(LocallyGrippedObjects[i].GrippedObject) && LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (LocallyGrippedObjects[i].GripCollisionType != EGripCollisionType::CustomGrip && LocallyGrippedObjects[i].GripCollisionType != EGripCollisionType::EventsOnly) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(LocallyGrippedObjects[i].GrippedObject); + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + bNeedsPhysicsTick = true; + break; + } + } + } + } + + if (!bNeedsPhysicsTick) + { + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (IsValid(GrippedObjects[i].GrippedObject) && GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (GrippedObjects[i].GripCollisionType != EGripCollisionType::CustomGrip && GrippedObjects[i].GripCollisionType != EGripCollisionType::EventsOnly) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(GrippedObjects[i].GrippedObject); + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + bNeedsPhysicsTick = true; + break; + } + } + } + } + } + } + } + + if (!bNeedsPhysicsTick) + { + RegisterEndPhysicsTick(false); + } + } +} + +bool UGripMotionControllerComponent::BP_IsLocallyControlled() +{ + return IsLocallyControlled(); +} + +bool UGripMotionControllerComponent::BP_HasGripAuthority(const FBPActorGripInformation &Grip) +{ + return HasGripAuthority(Grip); +} + +bool UGripMotionControllerComponent::BP_HasGripAuthorityForObject(const UObject * ObjToCheck) +{ + return HasGripAuthority(ObjToCheck); +} + +bool UGripMotionControllerComponent::BP_HasGripMovementAuthority(const FBPActorGripInformation &Grip) +{ + return HasGripMovementAuthority(Grip); +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentPoint(UObject * GrippedObjectToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform & OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime,/* float SecondarySmoothingScaler,*/ bool bIsSlotGrip, FName SecondarySlotName) +{ + if (!GrippedObjectToAddAttachment || !SecondaryPointComponent || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripToUse = nullptr; + + GripToUse = LocallyGrippedObjects.FindByKey(GrippedObjectToAddAttachment); + + // Search replicated grips if not found in local + if (!GripToUse) + { + // Replicated grips need to be called from server side + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on the client side with a replicated grip")); + return false; + } + + GripToUse = GrippedObjects.FindByKey(GrippedObjectToAddAttachment); + } + + if (GripToUse) + { + return AddSecondaryAttachmentToGrip(*GripToUse, SecondaryPointComponent, OriginalTransform, bTransformIsAlreadyRelative, LerpToTime, bIsSlotGrip, SecondarySlotName); + } + + return false; +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentToGripByID(const uint8 GripID, USceneComponent* SecondaryPointComponent, const FTransform& OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime, bool bIsSlotGrip, FName SecondarySlotName) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + return AddSecondaryAttachmentToGrip(*GripToUse, SecondaryPointComponent, OriginalTransform, bTransformIsAlreadyRelative, LerpToTime, bIsSlotGrip, SecondarySlotName); + } + } + + return false; +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentToGrip(const FBPActorGripInformation & GripToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime, bool bIsSlotGrip, FName SecondarySlotName) +{ + if (!SecondaryPointComponent) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad secondary component target!")); + return false; + } + + FBPActorGripInformation* GripToUse = nullptr; + bool bWasLocal = false; + if (GripToAddAttachment.GrippedObject && GripToAddAttachment.GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripToAddAttachment.GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripToAddAttachment.GripID); + bWasLocal = true; + } + } + + if (!GripToUse || GripToUse->GripID == INVALID_VRGRIP_ID) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad grip! It was not valid / found.")); + return false; + } + + if (!GripToUse->GrippedObject) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad grip (gripped object invalid)!")); + return false; + } + + // Replicated grips need to be called from server side + if (!bWasLocal && !IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on the client side with a replicated grip")); + return false; + } + + bool bGrippedObjectIsInterfaced = GripToUse->GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + if (bGrippedObjectIsInterfaced) + { + ESecondaryGripType SecondaryType = IVRGripInterface::Execute_SecondaryGripType(GripToUse->GrippedObject); + + if (SecondaryType == ESecondaryGripType::SG_None) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on an interface object set to SG_None!")); + return false; + } + } + + UPrimitiveComponent * root = nullptr; + + switch (GripToUse->GripTargetType) + { + case EGripTargetType::ActorGrip: + { + AActor * pActor = GripToUse->GetGrippedActor(); + + if (pActor) + { + root = Cast(pActor->GetRootComponent()); + } + } + break; + case EGripTargetType::ComponentGrip: + { + root = GripToUse->GetGrippedComponent(); + } + break; + } + + if (!root) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was unable to get root component or gripped component.")); + return false; + } + + if (bTransformIsAlreadyRelative) + GripToUse->SecondaryGripInfo.SecondaryRelativeTransform = OriginalTransform; + else + GripToUse->SecondaryGripInfo.SecondaryRelativeTransform = OriginalTransform.GetRelativeTransform(root->GetComponentTransform()); + + GripToUse->SecondaryGripInfo.SecondaryAttachment = SecondaryPointComponent; + GripToUse->SecondaryGripInfo.bHasSecondaryAttachment = true; + GripToUse->SecondaryGripInfo.SecondaryGripDistance = 0.0f; + GripToUse->SecondaryGripInfo.SecondarySlotName = SecondarySlotName; + + /*const UVRGlobalSettings& VRSettings = *GetDefault(); + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.ResetSmoothingFilter();*/ + // GripToUse->SecondaryGripInfo.SecondarySmoothingScaler = FMath::Clamp(SecondarySmoothingScaler, 0.01f, 1.0f); + GripToUse->SecondaryGripInfo.bIsSlotGrip = bIsSlotGrip; + + if (GripToUse->SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + LerpToTime = 0.0f; + + if (LerpToTime > 0.0f) + { + GripToUse->SecondaryGripInfo.LerpToRate = LerpToTime; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + GripToUse->SecondaryGripInfo.curLerp = LerpToTime; + } + + if (bGrippedObjectIsInterfaced) + { + SecondaryGripIDs.Add(GripToUse->GripID); + + IVRGripInterface::Execute_OnSecondaryGrip(GripToUse->GrippedObject, this, SecondaryPointComponent, *GripToUse); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(GripToUse->GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, SecondaryPointComponent, *GripToUse); + } + } + } + } + + if (GripToUse->GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive && !IsServer() && !IsTornOff()) + { + Server_NotifySecondaryAttachmentChanged(GripToUse->GripID, GripToUse->SecondaryGripInfo); + } + + OnSecondaryGripAdded.Broadcast(*GripToUse); + GripToUse = nullptr; + + return true; +} + +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentPoint(UObject * GrippedObjectToRemoveAttachment, float LerpToTime) +{ + if (!GrippedObjectToRemoveAttachment || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripToUse = nullptr; + + // Duplicating the logic for each array for now + GripToUse = LocallyGrippedObjects.FindByKey(GrippedObjectToRemoveAttachment); + + // Check replicated grips if it wasn't found in local + if (!GripToUse) + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController remove secondary attachment function was called on the client side for a replicating grip")); + return false; + } + + GripToUse = GrippedObjects.FindByKey(GrippedObjectToRemoveAttachment); + } + + // Handle the grip if it was found + if (GripToUse && GripToUse->GrippedObject) + { + return RemoveSecondaryAttachmentFromGrip(*GripToUse, LerpToTime); + } + + return false; +} +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentFromGripByID(const uint8 GripID, float LerpToTime) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + return RemoveSecondaryAttachmentFromGrip(*GripToUse, LerpToTime); + } + } + + return false; +} + +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentFromGrip(const FBPActorGripInformation & GripToRemoveAttachment, float LerpToTime) +{ + FBPActorGripInformation* GripToUse = nullptr; + bool bWasLocal = false; + if (GripToRemoveAttachment.GrippedObject && GripToRemoveAttachment.GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripToRemoveAttachment.GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripToRemoveAttachment.GripID); + bWasLocal = true; + } + } + + if (GripToUse && !bWasLocal && !IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController remove secondary attachment function was called on the client side for a replicating grip")); + return false; + } + + // Handle the grip if it was found + if (GripToUse && GripToUse->GrippedObject && GripToUse->GripID != INVALID_VRGRIP_ID) + { + SecondaryGripIDs.Remove(GripToUse->GripID); + + if (GripToUse->SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) + LerpToTime = 0.0f; + + //if (LerpToTime > 0.0f) + //{ + UPrimitiveComponent * primComp = nullptr; + + switch (GripToUse->GripTargetType) + { + case EGripTargetType::ComponentGrip: + { + primComp = GripToUse->GetGrippedComponent(); + }break; + case EGripTargetType::ActorGrip: + { + AActor * pActor = GripToUse->GetGrippedActor(); + if (pActor) + primComp = Cast(pActor->GetRootComponent()); + } break; + } + + bool bGripObjectHasInterface = GripToUse->GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + if (bGripObjectHasInterface) + { + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(GripToUse->GrippedObject); + //else if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling) + //LerpToTime = 0.0f; + } + + if (primComp) + { + switch (SecondaryType) + { + // All of these retain the position on release + case ESecondaryGripType::SG_FreeWithScaling_Retain: + case ESecondaryGripType::SG_SlotOnlyWithScaling_Retain: + case ESecondaryGripType::SG_Free_Retain: + case ESecondaryGripType::SG_SlotOnly_Retain: + case ESecondaryGripType::SG_ScalingOnly: + { + GripToUse->RelativeTransform = primComp->GetComponentTransform().GetRelativeTransform(GetPivotTransform()); + GripToUse->SecondaryGripInfo.LerpToRate = 0.0f; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + }break; + default: + { + if (LerpToTime > 0.0f) + { + // #TODO: This had a hitch in it just prior to lerping back, fix it eventually and allow lerping from scaling secondaries + //GripToUse->RelativeTransform.SetScale3D(GripToUse->RelativeTransform.GetScale3D() * FVector(GripToUse->SecondaryScaler)); + GripToUse->SecondaryGripInfo.LerpToRate = LerpToTime; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::EndLerp; + GripToUse->SecondaryGripInfo.curLerp = LerpToTime; + } + }break; + } + + } + else + { + GripToUse->SecondaryGripInfo.LerpToRate = 0.0f; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + if (bGripObjectHasInterface) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(GripToUse->GrippedObject, this, GripToUse->SecondaryGripInfo.SecondaryAttachment, *GripToUse); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(GripToUse->GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGripRelease(this, GripToUse->SecondaryGripInfo.SecondaryAttachment, *GripToUse); + } + } + } + } + + GripToUse->SecondaryGripInfo.SecondaryAttachment = nullptr; + GripToUse->SecondaryGripInfo.bHasSecondaryAttachment = false; + + if (GripToUse->GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive && !IsServer()) + { + switch (SecondaryType) + { + // All of these retain the position on release + case ESecondaryGripType::SG_FreeWithScaling_Retain: + case ESecondaryGripType::SG_SlotOnlyWithScaling_Retain: + case ESecondaryGripType::SG_Free_Retain: + case ESecondaryGripType::SG_SlotOnly_Retain: + case ESecondaryGripType::SG_ScalingOnly: + { + if (!IsTornOff()) + Server_NotifySecondaryAttachmentChanged_Retain(GripToUse->GripID, GripToUse->SecondaryGripInfo, GripToUse->RelativeTransform); + }break; + default: + { + if (!IsTornOff()) + Server_NotifySecondaryAttachmentChanged(GripToUse->GripID, GripToUse->SecondaryGripInfo); + }break; + } + + } + + SecondaryGripIDs.Remove(GripToUse->GripID); + OnSecondaryGripRemoved.Broadcast(*GripToUse); + GripToUse = nullptr; + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::TeleportMoveGrippedActor(AActor * GrippedActorToMove, bool bTeleportPhysicsGrips) +{ + if (!GrippedActorToMove || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GrippedActorToMove); + if (!GripInfo) + GrippedObjects.FindByKey(GrippedActorToMove); + + if (GripInfo) + { + return TeleportMoveGrip(*GripInfo, bTeleportPhysicsGrips); + } + + return false; +} + +bool UGripMotionControllerComponent::TeleportMoveGrippedComponent(UPrimitiveComponent * ComponentToMove, bool bTeleportPhysicsGrips) +{ + if (!ComponentToMove || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(ComponentToMove); + if (!GripInfo) + GrippedObjects.FindByKey(ComponentToMove); + + if (GripInfo) + { + return TeleportMoveGrip(*GripInfo, bTeleportPhysicsGrips); + } + + return false; +} + +void UGripMotionControllerComponent::TeleportMoveGrips(bool bTeleportPhysicsGrips, bool bIsForPostTeleport) +{ + FTransform EmptyTransform = FTransform::Identity; + for (FBPActorGripInformation& GripInfo : LocallyGrippedObjects) + { + TeleportMoveGrip_Impl(GripInfo, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); + } + + for (FBPActorGripInformation& GripInfo : GrippedObjects) + { + TeleportMoveGrip_Impl(GripInfo, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); + } +} + +bool UGripMotionControllerComponent::TeleportMoveGrip(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport) +{ + FTransform EmptyTransform = FTransform::Identity; + return TeleportMoveGrip_Impl(Grip, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); +} + +bool UGripMotionControllerComponent::TeleportMoveGrip_Impl(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport, FTransform & OptionalTransform) +{ + bool bHasMovementAuthority = HasGripMovementAuthority(Grip); + + if (!bHasMovementAuthority) + return false; + + UPrimitiveComponent * PrimComp = NULL; + AActor * actor = NULL; + + // Check if either implements the interface + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + switch (Grip.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + actor = Grip.GetGrippedActor(); + if (actor) + { + PrimComp = Cast(actor->GetRootComponent()); + if (actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bActorHasInterface = true; + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + PrimComp = Grip.GetGrippedComponent(); + + if (PrimComp) + { + actor = PrimComp->GetOwner(); + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + } + + }break; + + } + + if (!PrimComp || !actor || !IsValid(actor) || !IsValid(PrimComp)) + return false; + + // Only use with actual teleporting + + EGripInterfaceTeleportBehavior TeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + bool bSimulateOnDrop = false; + + // Check for interaction interface + if (bRootHasInterface) + { + TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(PrimComp); + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(PrimComp); + } + else if (bActorHasInterface) + { + // Actor grip interface is checked after component + TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(actor); + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(actor); + } + + if (bIsForPostTeleport) + { + if (TeleportBehavior == EGripInterfaceTeleportBehavior::OnlyTeleportRootComponent) + { + if (AActor * owner = PrimComp->GetOwner()) + { + if (PrimComp != owner->GetRootComponent()) + { + return false; + } + } + } + else if (TeleportBehavior == EGripInterfaceTeleportBehavior::DropOnTeleport) + { + if (IsServer() || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + DropObjectByInterface(nullptr, Grip.GripID); + } + + return false; // Didn't teleport + } + else if (TeleportBehavior == EGripInterfaceTeleportBehavior::DontTeleport) + { + return false; // Didn't teleport + } + } + else + { + switch (TeleportBehavior) + { + case EGripInterfaceTeleportBehavior::DontTeleport: + case EGripInterfaceTeleportBehavior::DropOnTeleport: + { + return false; + }break; + default:break; + } + } + + // We don't teleport these two grip types at all + if (Grip.GripCollisionType == EGripCollisionType::AttachmentGrip || Grip.GripCollisionType == EGripCollisionType::EventsOnly) + { + return false; + } + + FTransform WorldTransform; + FTransform ParentTransform = GetPivotTransform(); + + FBPActorGripInformation copyGrip = Grip; + + if (!OptionalTransform.Equals(FTransform::Identity)) + { + WorldTransform = OptionalTransform; + } + else + { + TArray Scripts; + + if (bRootHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(PrimComp, Scripts); + } + else if (bActorHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(actor, Scripts); + } + + bool bForceADrop = false; + bool bHadValidWorldTransform = GetGripWorldTransform(Scripts, 0.0f, WorldTransform, ParentTransform, copyGrip, actor, PrimComp, bRootHasInterface, bActorHasInterface, true, bForceADrop); + + if (!bHadValidWorldTransform) + return false; + } + + if (!WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, TeleportGrip_Impl's target transform contained NAN.")); + return false; + } + + // Saving this out prior as we are still setting our physics thread to the correct value, the delta is only applied to the object + FTransform physicsTrans = WorldTransform; + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation && !Grip.LastWorldTransform.Equals(FTransform::Identity)) + { + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + WorldTransform = Grip.LastWorldTransform * baseTrans; + + //physicsTrans = WorldTransform; + + // Cancel out all other holding controllers teleport operations, we hit first + if (!Grip.bSkipNextTeleportCheck && (bRootHasInterface || bActorHasInterface)) + { + TArray HoldingControllers; + bool bIsHeld = false; + IVRGripInterface::Execute_IsHeld(Grip.GrippedObject, HoldingControllers, bIsHeld); + + for (FBPGripPair pair : HoldingControllers) + { + if (pair.HoldingController && pair.HoldingController != this && pair.HoldingController->bIsPostTeleport) + { + FBPActorGripInformation* pGrip = pair.HoldingController->GetGripPtrByID(pair.GripID); + + if (pGrip) + { + pGrip->bSkipNextTeleportCheck = true; + } + } + } + } + } + + // Run some error checks and logging against the resulting transform + if (!WorldTransform.IsValid()) + { + if (WorldTransform.ContainsNaN()) + { + UE_LOG(LogVRMotionController, Error, TEXT("Failed to teleport grip, bad transform, NaN detected with object: %s"), *Grip.GrippedObject->GetName()); + return false; + } + else if (!WorldTransform.GetRotation().IsNormalized()) + { + WorldTransform.NormalizeRotation(); + + if (!WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Error, TEXT("Failed to teleport grip, bad transform, rotation normalization issue: %s"), *Grip.GrippedObject->GetName()); + return false; + } + else + { + UE_LOG(LogVRMotionController, Error, TEXT("Error during teleport grip, rotation not normalized for object: %s"), *Grip.GrippedObject->GetName()); + } + } + } + + // Need to use WITH teleport for this function so that the velocity isn't updated and without sweep so that they don't collide + FBPActorPhysicsHandleInformation * Handle = GetPhysicsGrip(Grip); + + if (!Handle) + { + PrimComp->SetWorldTransform(WorldTransform, bSweepGripTeleports, nullptr, ETeleportType::TeleportPhysics); + } + else if (Handle && FPhysicsInterface::IsValid(Handle->KinActorData2) && bTeleportPhysicsGrips) + { + + // Don't try to autodrop on next tick, let the physx constraint update its local frame first + if (HasGripAuthority(Grip)) + Grip.bSkipNextConstraintLengthCheck = true; + + if (Grip.bSkipNextTeleportCheck) + { + Grip.bSkipNextTeleportCheck = false; + } + else + { + PrimComp->SetWorldTransform(WorldTransform, bSweepGripTeleports, nullptr, ETeleportType::TeleportPhysics); + } + + // Zero out our scale now that we are working outside of physx + physicsTrans.SetScale3D(FVector(1.0f)); + + if (Grip.bIsLerping || !bConstrainToPivot) + { + FBodyInstance* pInstance = PrimComp->GetBodyInstance(); + FPhysicsActorHandle ActorHandle = Handle->KinActorData2; + FTransform newTrans = Handle->COMPosition * (Handle->RootBoneRotation * physicsTrans); + if (pInstance && pInstance->IsValidBodyInstance()) + { + if (FPhysScene* PhysicalScene = pInstance->GetPhysicsScene()) + { + //FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle& Actor) + FPhysicsCommand::ExecuteWrite(PhysicalScene, [&]() + { + if (FPhysicsInterface::IsValid(ActorHandle)) + { + FPhysicsInterface::SetKinematicTarget_AssumesLocked(ActorHandle, newTrans); + FPhysicsInterface::SetGlobalPose_AssumesLocked(ActorHandle, newTrans); + } + }); + } + } + } + } + + return true; +} + +void UGripMotionControllerComponent::PostTeleportMoveGrippedObjects() +{ + if (!GrippedObjects.Num() && !LocallyGrippedObjects.Num()) + return; + + this->bIsPostTeleport = true; +} + + +void UGripMotionControllerComponent::Deactivate() +{ + Super::Deactivate(); + + if (IsActive() == false && GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } +} + +void UGripMotionControllerComponent::OnAttachmentChanged() +{ + if (AVRCharacter* CharacterOwner = Cast(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +void UGripMotionControllerComponent::OnRep_ReplicatedControllerTransform() +{ + //ReplicatedControllerTransform.Unpack(); + + if (IsServer() && HasTrackingParameters()) + { + // Ensure that the client is sending valid boundries + ApplyTrackingParameters(ReplicatedControllerTransform.Position, true, false); + } + + if (bSmoothReplicatedMotion) + { + if (bReppedOnce) + { + bLerpingPosition = true; + ControllerNetUpdateCount = 0.0f; + LastUpdatesRelativePosition = this->GetRelativeLocation(); + LastUpdatesRelativeRotation = this->GetRelativeRotation(); + + if (bUseExponentialSmoothing) + { + FVector OldToNewVector = ReplicatedControllerTransform.Position - LastUpdatesRelativePosition; + float NewDistance = OldToNewVector.SizeSquared(); + + // Too far, snap to the new value + if (NewDistance >= FMath::Square(NetworkNoSmoothUpdateDistance)) + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + bLerpingPosition = false; + } + // Outside of the buffer distance, snap within buffer and keep smoothing from there + else if (NewDistance >= FMath::Square(NetworkMaxSmoothUpdateDistance)) + { + FVector Offset = (OldToNewVector.Size() - NetworkMaxSmoothUpdateDistance) * OldToNewVector.GetSafeNormal(); + SetRelativeLocation(LastUpdatesRelativePosition + Offset); + } + } + } + else + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + bReppedOnce = true; + } + } + else + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); +} + +void UGripMotionControllerComponent::UpdateTracking(float DeltaTime) +{ + // Server/remote clients don't set the controller position in VR + // Don't call positional checks and don't create the late update scene view + if (bHasAuthority) + { + if (bOffsetByControllerProfile && !NewControllerProfileEvent_Handle.IsValid()) + { + GetCurrentProfileTransform(true); + } + + FVector Position = GetRelativeLocation(); + FRotator Orientation = GetRelativeRotation(); + + if (!bUseWithoutTracking) + { + if (!GripViewExtension.IsValid() && GEngine) + { + GripViewExtension = FSceneViewExtensions::NewExtension(this); + } + + float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f; + ETrackingStatus LastTrackingStatus = CurrentTrackingStatus; + const bool bNewTrackedState = PollControllerState_GameThread(Position, Orientation, bProvidedLinearVelocity, LinearVelocity, bProvidedAngularVelocity, AngularVelocityAsAxisAndLength, bProvidedLinearAcceleration, LinearAcceleration, WorldToMeters); + + // if controller tracking just kicked in or we haven't started rendering in the (possibly present) + // visualization component. + if (!bTracked && bNewTrackedState) + { + OnActivateVisualizationComponent.Broadcast(true); + } + + + bTracked = bNewTrackedState && (bIgnoreTrackingStatus || CurrentTrackingStatus != ETrackingStatus::NotTracked); + if (bTracked) + { + if (bSmoothHandTracking) + { + FTransform CalcedTransform = FTransform(Orientation, Position, this->GetRelativeScale3D()); + + if (bSmoothWithEuroLowPassFunction) + { + SetRelativeTransform(EuroSmoothingParams.RunFilterSmoothing(CalcedTransform, DeltaTime)); + } + else + { + if (SmoothingSpeed <= 0.f || LastSmoothRelativeTransform.Equals(FTransform::Identity)) + { + SetRelativeTransform(CalcedTransform); + LastSmoothRelativeTransform = CalcedTransform; + } + else + { + const float Alpha = FMath::Clamp(DeltaTime * SmoothingSpeed, 0.f, 1.f); + LastSmoothRelativeTransform.Blend(LastSmoothRelativeTransform, CalcedTransform, Alpha); + SetRelativeTransform(LastSmoothRelativeTransform); + } + } + + bWasSmoothingHand = true; + } + else + { + if (bWasSmoothingHand) + { + // Clear the smoothing information so that we start with a fresh log when its enabled again + LastSmoothRelativeTransform = FTransform::Identity; + EuroSmoothingParams.ResetSmoothingFilter(); + + bWasSmoothingHand = false; + } + + SetRelativeTransform(FTransform(Orientation, Position, this->GetRelativeScale3D())); + } + } + + // if controller tracking just changed + if (LastTrackingStatus != CurrentTrackingStatus) + { + OnTrackingChanged.Broadcast(CurrentTrackingStatus); + } + } + + if (!bTracked && !bUseWithoutTracking) + return; // Don't update anything including location + + // Don't bother with any of this if not replicating transform + if (GetIsReplicated() && (bTracked || bReplicateWithoutTracking)) + { + FVector RelLoc = GetRelativeLocation(); + FRotator RelRot = GetRelativeRotation(); + + // Don't rep if no changes + if (!RelLoc.Equals(ReplicatedControllerTransform.Position) || !RelRot.Equals(ReplicatedControllerTransform.Rotation)) + { + ControllerNetUpdateCount += DeltaTime; + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) + { + ControllerNetUpdateCount = 0.0f; + + // Tracked doesn't matter, already set the relative location above in that case + ReplicatedControllerTransform.Position = RelLoc; + ReplicatedControllerTransform.Rotation = RelRot; + + // I would keep the torn off check here, except this can be checked on tick if they + // Set 100 htz updates, and in the TornOff case, it actually can't hurt any besides some small + // Perf difference. + if (!IsServer()/* && !IsTornOff()*/) + { + AVRBaseCharacter* OwningChar = Cast(GetOwner()); + if (OverrideSendTransform != nullptr && OwningChar != nullptr) + { + (OwningChar->* (OverrideSendTransform))(ReplicatedControllerTransform); + } + else + Server_SendControllerTransform(ReplicatedControllerTransform); + } + } + } + } + } + else + { + // Clear the view extension if active after unpossessing, just in case + if (GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent and allowing the destructor to continue + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } + + // Run any networked smoothing + RunNetworkedSmoothing(DeltaTime); + } +} + +void UGripMotionControllerComponent::RunNetworkedSmoothing(float DeltaTime) +{ + if (bLerpingPosition) + { + if (!bUseExponentialSmoothing) + { + ControllerNetUpdateCount += DeltaTime; + float LerpVal = FMath::Clamp(ControllerNetUpdateCount / (1.0f / ControllerNetUpdateRate), 0.0f, 1.0f); + + if (LerpVal >= 1.0f) + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + + // Stop lerping, wait for next update if it is delayed or lost then it will hitch here + // Actual prediction might be something to consider in the future, but rough to do in VR + // considering the speed and accuracy of movements + // would like to consider sub stepping but since there is no server rollback...not sure how useful it would be + // and might be perf taxing enough to not make it worth it. + bLerpingPosition = false; + ControllerNetUpdateCount = 0.0f; + } + else + { + // Removed variables to speed this up a bit + SetRelativeLocationAndRotation( + FMath::Lerp(LastUpdatesRelativePosition, (FVector)ReplicatedControllerTransform.Position, LerpVal), + FMath::Lerp(LastUpdatesRelativeRotation, ReplicatedControllerTransform.Rotation, LerpVal) + ); + } + } + else // Exponential Smoothing + { + if (InterpolationSpeed <= 0.f) + { + SetRelativeLocationAndRotation((FVector)ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + bLerpingPosition = false; + return; + } + + const float Alpha = FMath::Clamp(DeltaTime * InterpolationSpeed, 0.f, 1.f); + + FTransform NA = FTransform(GetRelativeRotation(), GetRelativeLocation(), FVector(1.0f)); + FTransform NB = FTransform(ReplicatedControllerTransform.Rotation, (FVector)ReplicatedControllerTransform.Position, FVector(1.0f)); + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + NA.Blend(NA, NB, Alpha); + + // If we are nearly equal then snap to final position + if (NA.EqualsNoScale(NB)) + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + bLerpingPosition = false; + } + else // Else just keep going + { + SetRelativeLocationAndRotation(NA.GetTranslation(), NA.Rotator()); + } + } + } +} + +void UGripMotionControllerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Skip if we are traveling + if (IsTravelingOrNullWorld()) + return; + + // Skip motion controller tick, we override a lot of things that it does and we don't want it to perform the same functions + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (!IsActive()) + return; + + // Moved this here instead of in the polling function, it was ticking once per frame anyway so no loss of perf + // It doesn't need to be there and now I can pre-check + // Also epics implementation in the polling function didn't work anyway as it was based off of playercontroller which is not the owner of this controller + + // Cache state from the game thread for use on the render thread + // No need to check if in game thread here as tick always is + bHasAuthority = IsLocallyControlled(); + + // No longer updating in character, was a waste as it wouldn't scope this component anyway + UpdateTracking(DeltaTime); + + /*if (!bUpdateInCharacterMovement) + { + UpdateTracking(DeltaTime); + } + else if (AttachChar.IsValid()) + { + UCharacterMovementComponent* CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive() || (GetWorld()->IsPaused() && !AttachChar->GetCharacterMovement()->PrimaryComponentTick.bTickEvenWhenPaused)) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + }*/ + + // Process the gripped actors + TickGrip(DeltaTime); +} + +bool UGripMotionControllerComponent::GetGripWorldTransform(TArray& GripScripts, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport, bool &bForceADrop) +{ + SCOPE_CYCLE_COUNTER(STAT_GetGripTransform); + + bool bHasValidTransform = true; + + if (GripScripts.Num()) + { + bool bGetDefaultTransform = true; + + // Get grip script world transform overrides (if there are any) + for (UVRGripScriptBase* Script: GripScripts) + { + if (Script && Script->IsScriptActive() && Script->GetWorldTransformOverrideType() == EGSTransformOverrideType::OverridesWorldTransform) + { + // One of the grip scripts overrides the default transform + bGetDefaultTransform = false; + break; + } + } + + // If none of the scripts override the base transform + if (bGetDefaultTransform && DefaultGripScript) + { + bHasValidTransform = DefaultGripScript->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = DefaultGripScript->Wants_ToForceDrop(); + } + + // Get grip script world transform modifiers (if there are any) + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->GetWorldTransformOverrideType() != EGSTransformOverrideType::None) + { + bHasValidTransform = Script->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = Script->Wants_ToForceDrop(); + + // Early out, one of the scripts is telling us that the transform isn't valid, something went wrong or the grip is flagged for drop + if (!bHasValidTransform || bForceADrop) + break; + } + } + } + else + { + if (DefaultGripScript) + { + bHasValidTransform = DefaultGripScript->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = DefaultGripScript->Wants_ToForceDrop(); + } + } + + HandleGlobalLerpToHand(Grip, WorldTransform, DeltaTime); + + if (bHasValidTransform && !WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, GetGripWorldTransform tried to return NAN!.")); + bHasValidTransform = false; + } + + return bHasValidTransform; +} + +void UGripMotionControllerComponent::TickGrip(float DeltaTime) +{ + SCOPE_CYCLE_COUNTER(STAT_TickGrip); + + // Debug test that we aren't floating physics handles + if (PhysicsGrips.Num() > (GrippedObjects.Num() + LocallyGrippedObjects.Num())) + { + CleanUpBadPhysicsHandles(); + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, there were too many physics handles for how many grips exist! Cleaned up bad handles.")); + } + //check(PhysicsGrips.Num() <= (GrippedObjects.Num() + LocallyGrippedObjects.Num())); + + FTransform ParentTransform = GetPivotTransform(); + + // Check for floating server sided client auth grips and handle them if we need too + if(!IsServer()) + CheckTransactionBuffer(); + + bool bOriginalPostTeleport = bIsPostTeleport; + + // Split into separate functions so that I didn't have to combine arrays since I have some removal going on + HandleGripArray(GrippedObjects, ParentTransform, DeltaTime, true); + HandleGripArray(LocallyGrippedObjects, ParentTransform, DeltaTime); + + // Empty out the teleport flag, checking original state just in case the player changed it while processing bps + if (bOriginalPostTeleport) + { + if ((GrippedObjects.Num() || LocallyGrippedObjects.Num())) + { + OnTeleportedGrips.Broadcast(); + } + + bIsPostTeleport = false; + } + + // Save out the component velocity from this and last frame + + FVector newVelocitySample = ((bSampleVelocityInWorldSpace ? GetComponentLocation() : GetRelativeLocation()) - LastRelativePosition.GetTranslation()) / DeltaTime; + + switch (VelocityCalculationType) + { + case EVRVelocityType::VRLOCITY_Default: + { + ComponentVelocity = newVelocitySample; + }break; + case EVRVelocityType::VRLOCITY_RunningAverage: + { + UVRExpansionFunctionLibrary::LowPassFilter_RollingAverage(ComponentVelocity, newVelocitySample, ComponentVelocity, VelocitySamples); + }break; + case EVRVelocityType::VRLOCITY_SamplePeak: + { + if (PeakFilter.VelocitySamples != VelocitySamples) + PeakFilter.VelocitySamples = VelocitySamples; + UVRExpansionFunctionLibrary::UpdatePeakLowPassFilter(PeakFilter, newVelocitySample); + }break; + } + + // #TODO: + // Relative angular velocity too? + // Maybe add some running averaging here to make it work across frames? + // Or Valves 30 frame high point average buffer + LastRelativePosition = bSampleVelocityInWorldSpace ? this->GetComponentTransform() : this->GetRelativeTransform(); +} + +FVector UGripMotionControllerComponent::GetComponentVelocity() const +{ + if(VelocityCalculationType == EVRVelocityType::VRLOCITY_SamplePeak) + { + return PeakFilter.GetPeak(); + } + + return Super::GetComponentVelocity(); +} + +void UGripMotionControllerComponent::HandleGripArray(TArray &GrippedObjectsArray, const FTransform & ParentTransform, float DeltaTime, bool bReplicatedArray) +{ + if (GrippedObjectsArray.Num()) + { + FTransform WorldTransform; + + for (int i = GrippedObjectsArray.Num() - 1; i >= 0; --i) + { + if (!HasGripMovementAuthority(GrippedObjectsArray[i])) + continue; + + FBPActorGripInformation * Grip = &GrippedObjectsArray[i]; + + if (!Grip) // Shouldn't be possible, but why not play it safe + continue; + + // Double checking here for a failed rep due to out of order replication from a spawned actor + if (!Grip->ValueCache.bWasInitiallyRepped && !HasGripAuthority(*Grip) && !HandleGripReplication(*Grip)) + continue; // If we didn't successfully handle the replication (out of order) then continue on. + + if (Grip->IsValid()) + { + // Continue if the grip is paused + if (Grip->bIsPaused) + continue; + + if (Grip->GripCollisionType == EGripCollisionType::EventsOnly) + continue; // Earliest safe spot to continue at, we needed to check if the object is pending kill or invalid first + + UPrimitiveComponent *root = NULL; + AActor *actor = NULL; + + // Getting the correct variables depending on the grip target type + switch (Grip->GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + actor = Grip->GetGrippedActor(); + if(actor) + root = Cast(actor->GetRootComponent()); + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip : + { + root = Grip->GetGrippedComponent(); + if(root) + actor = root->GetOwner(); + }break; + + default:break; + } + + // Last check to make sure the variables are valid + if (!root || !actor || !IsValid(root) || !IsValid(actor)) + continue; + + // Keep checking for pending kill on gripped objects, and ptr removals, but don't run grip logic when seamless + // traveling, to avoid physx scene issues. + if (GetWorld()->IsInSeamlessTravel()) + { + continue; + } + + // Check if either implements the interface + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + else if (actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (Grip->GripCollisionType == EGripCollisionType::CustomGrip) + { + // Don't perform logic on the movement for this object, just pass in the GripTick() event with the controller difference instead + if(bRootHasInterface) + IVRGripInterface::Execute_TickGrip(root, this, *Grip, DeltaTime); + else if(bActorHasInterface) + IVRGripInterface::Execute_TickGrip(actor, this, *Grip, DeltaTime); + + continue; + } + + bool bRescalePhysicsGrips = false; + + TArray GripScripts; + + if (bRootHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(root, GripScripts); + } + else if (bActorHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(actor, GripScripts); + } + + + bool bForceADrop = false; + + // Get the world transform for this grip after handling secondary grips and interaction differences + bool bHasValidWorldTransform = GetGripWorldTransform(GripScripts, DeltaTime, WorldTransform, ParentTransform, *Grip, actor, root, bRootHasInterface, bActorHasInterface, false, bForceADrop); + + // If a script or behavior is telling us to skip this and continue on (IE: it dropped the grip) + if (bForceADrop) + { + if (HasGripAuthority(*Grip)) + { + if (bRootHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(root)); + else if (bActorHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(actor)); + else + DropGrip_Implementation(*Grip, true); + } + + continue; + } + else if (!bHasValidWorldTransform) + { + continue; + } + + if (Grip->GrippedBoneName == NAME_None && !root->GetComponentTransform().GetScale3D().Equals(WorldTransform.GetScale3D())) + bRescalePhysicsGrips = true; + + // If we just teleported, skip this update and just teleport forward + if (bIsPostTeleport) + { + + bool bSkipTeleport = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyTeleport(this)) + { + bSkipTeleport = true; + break; + } + } + + + if (!bSkipTeleport) + { + TeleportMoveGrip_Impl(*Grip, true, true, WorldTransform); + continue; + } + } + else + { + //Grip->LastWorldTransform = WorldTransform; + } + + // Auto drop based on distance from expected point + // Not perfect, should be done post physics or in next frame prior to changing controller location + // However I don't want to recalculate world transform + // Maybe add a grip variable of "expected loc" and use that to check next frame, but for now this will do. + if ((bRootHasInterface || bActorHasInterface) && + ( + (Grip->GripCollisionType != EGripCollisionType::AttachmentGrip) && + (Grip->GripCollisionType != EGripCollisionType::PhysicsOnly) && + (Grip->GripCollisionType != EGripCollisionType::SweepWithPhysics)) && + ((Grip->GripCollisionType != EGripCollisionType::InteractiveHybridCollisionWithSweep) || ((Grip->GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithSweep) && Grip->bColliding)) + ) + { + + // After initial teleportation the constraint local pose can be not updated yet, so lets delay a frame to let it update + // Otherwise may cause unintended auto drops + if (Grip->bSkipNextConstraintLengthCheck) + { + Grip->bSkipNextConstraintLengthCheck = false; + } + else + { + float BreakDistance = 0.0f; + if (bRootHasInterface) + { + BreakDistance = IVRGripInterface::Execute_GripBreakDistance(root); + } + else if (bActorHasInterface) + { + // Actor grip interface is checked after component + BreakDistance = IVRGripInterface::Execute_GripBreakDistance(actor); + } + + FVector CheckDistance; + if (!GetPhysicsJointLength(*Grip, root, CheckDistance)) + { + CheckDistance = (WorldTransform.GetLocation() - root->GetComponentLocation()); + } + + // Set grip distance now for people to use + Grip->GripDistance = CheckDistance.Size(); + + if (BreakDistance > 0.0f) + { + if (Grip->GripDistance >= BreakDistance) + { + bool bIgnoreDrop = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyAutoDrop()) + { + bIgnoreDrop = true; + break; + } + } + + if (bIgnoreDrop) + { + // Script canceled this out + } + else if (OnGripOutOfRange.IsBound()) + { + uint8 GripID = Grip->GripID; + OnGripOutOfRange.Broadcast(*Grip, Grip->GripDistance); + + // Check if we still have the grip or not + FBPActorGripInformation GripInfo; + EBPVRResultSwitch Result; + GetGripByID(GripInfo, GripID, Result); + if (Result == EBPVRResultSwitch::OnFailed) + { + // Don't bother moving it, it is dropped now + continue; + } + } + else if(HasGripAuthority(*Grip)) + { + if(bRootHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(root)); + else + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(actor)); + + // Don't bother moving it, it is dropped now + continue; + } + } + } + } + } + + // Start handling the grip types and their functions + switch (Grip->GripCollisionType) + { + case EGripCollisionType::InteractiveCollisionWithPhysics: + case EGripCollisionType::LockedConstraint: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + + // Sweep current collision state, only used for client side late update removal + if ( + (bHasAuthority && !this->bDisableLowLatencyUpdate && + ((Grip->GripLateUpdateSetting == EGripLateUpdateSettings::NotWhenColliding) || + (Grip->GripLateUpdateSetting == EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping))) + ) + { + //TArray Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + TArray Hits; + + // Switched over to component sweep because it picks up on pivot offsets without me manually calculating it + if ( + GetWorld()->ComponentSweepMulti(Hits, root, root->GetComponentLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params) + ) + { + + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + } + else + { + if (FHitResult::GetFirstBlockingHit(Hits) != nullptr) + { + Grip->bColliding = true; + } + } + } + else + { + Grip->bColliding = false; + } + } + + }break; + + case EGripCollisionType::InteractiveCollisionWithSweep: + { + FVector OriginalPosition(root->GetComponentLocation()); + FVector NewPosition(WorldTransform.GetTranslation()); + + if (!Grip->bIsLocked) + root->ComponentVelocity = (NewPosition - OriginalPosition) / DeltaTime; + + if (Grip->bIsLocked) + WorldTransform.SetRotation(Grip->LastLockedRotation); + + FHitResult OutHit; + // Need to use without teleport so that the physics velocity is updated for when the actor is released to throw + if (bProjectNonSimulatingGrips && !Grip->bIsLocked && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + root->SetWorldTransform(WorldTransform, true, &OutHit); + } + else + { + root->SetWorldTransform(WorldTransform, true, &OutHit); + } + + if (OutHit.bBlockingHit) + { + Grip->bColliding = true; + + if (!Grip->bIsLocked) + { + Grip->bIsLocked = true; + Grip->LastLockedRotation = root->GetComponentQuat(); + } + } + else + { + Grip->bColliding = false; + + if (Grip->bIsLocked) + Grip->bIsLocked = false; + } + }break; + + case EGripCollisionType::InteractiveHybridCollisionWithPhysics: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + // Always Sweep current collision state with this, used for constraint strength + //TArray Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + TArray Hits; + // Checking both current and next position for overlap using this grip type + // Switched over to component sweep because it picks up on pivot offsets without me manually calculating it + if (Grip->bLockHybridGrip) + { + if (!Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + + Grip->bColliding = true; + } + else if (GetWorld()->ComponentSweepMulti(Hits, root, root->GetComponentLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params) && FHitResult::GetFirstBlockingHit(Hits) != nullptr) + { + // Assume true by default, will revert if checking ignored below + Grip->bColliding = true; + + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + + bool bOriginalColliding = Grip->bColliding; + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + if (!bOriginalColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + Grip->bColliding = true; + break; + } + } + + if (!Grip->bColliding) + { + if (bOriginalColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, true); + } + } + + + } + else + { + if (!Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + //Grip->bColliding = true; + } + } + else + { + if (Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, true); + } + + Grip->bColliding = false; + } + + }break; + + case EGripCollisionType::InteractiveHybridCollisionWithSweep: + { + + // Make sure that there is no collision on course before turning off collision and snapping to controller + FBPActorPhysicsHandleInformation * GripHandle = GetPhysicsGrip(*Grip); + + TArray Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + FTransform BaseTransform = root->GetComponentTransform(); + + if (bProjectNonSimulatingGrips && !Grip->bColliding && Grip->bSetLastWorldTransform) + { + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + BaseTransform = Grip->LastWorldTransform * baseTrans; + } + + bool bWasColliding = Grip->bColliding; + bool bLerpCollisions = false; + bool bLerpRotationOnly = false; + bool bDistanceBasedInterpolation = false; + float LerpSpeed = 0.0f; + const UVRGlobalSettings* VRSettings = GetDefault(); + + if (VRSettings) + { + bLerpCollisions = VRSettings->bLerpHybridWithSweepGrips; + LerpSpeed = VRSettings->HybridWithSweepLerpDuration; + bLerpRotationOnly = VRSettings->bOnlyLerpHybridRotation; + bDistanceBasedInterpolation = VRSettings->bHybridWithSweepUseDistanceBasedLerp; + } + + if (Grip->bLockHybridGrip) + { + Grip->bColliding = true; + } + // Check our target rotation + else if (GetWorld()->ComponentSweepMulti(Hits, root, BaseTransform.GetLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params) && FHitResult::GetFirstBlockingHit(Hits) != nullptr) + { + // Assume true by default, will revert if checking ignored below + Grip->bColliding = true; + + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem(); + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + + // We need to also check the other rotation here as a fallback + if (bLerpCollisions && !Grip->bColliding) + { + if (bLerpCollisions && GetWorld()->ComponentSweepMulti(Hits, root, BaseTransform.GetLocation(), WorldTransform.GetLocation(), root->GetComponentRotation(), Params)) + { + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + } + } + } + } + // Check the other rotation + else if (bLerpCollisions && GetWorld()->ComponentSweepMulti(Hits, root, BaseTransform.GetLocation(), WorldTransform.GetLocation(), root->GetComponentRotation(), Params) && FHitResult::GetFirstBlockingHit(Hits) != nullptr) + { + // Assume true by default, will revert if checking ignored below + Grip->bColliding = true; + + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + } + } + else + { + Grip->bColliding = false; + } + + if (!Grip->bColliding) + { + if (GripHandle && !GripHandle->bIsPaused) + { + PausePhysicsHandle(GripHandle); + //DestroyPhysicsHandle(*Grip); + + switch (Grip->GripTargetType) + { + case EGripTargetType::ComponentGrip: + { + root->SetSimulatePhysics(false); + }break; + case EGripTargetType::ActorGrip: + { + root->SetSimulatePhysics(false); + //actor->DisableComponentsSimulatePhysics(); + } break; + } + } + + if (bLerpCollisions && !Grip->bIsLerping) + { + if (bWasColliding && !Grip->bIsLerping) + { + // Store relative transform and base movements off of lerping out of it to the target transform + + // Re-use this transform as it will let us not add additional variables + Grip->OnGripTransform = root->GetComponentTransform().GetRelativeTransform(this->GetPivotTransform()); + Grip->CurrentLerpTime = 1.0f; + Grip->LerpSpeed = (1.f / LerpSpeed); + + if (bDistanceBasedInterpolation) + { + // Just multiplying to make the values easier + Grip->LerpSpeed *= 10.0f; + Grip->CurrentLerpTime = LerpSpeed; + } + } + + if (Grip->CurrentLerpTime > 0.0f) + { + FTransform NB = (Grip->OnGripTransform * this->GetPivotTransform()); + float Alpha = 0.0f; + + if (bDistanceBasedInterpolation) + { + if (Grip->LerpSpeed <= 0.f) + { + Alpha = 1.0f; + Grip->CurrentLerpTime = 0.0f; + } + else + { + Grip->CurrentLerpTime = FMath::Clamp(Grip->CurrentLerpTime - DeltaTime, 0.0f, 1.0f); + Alpha = FMath::Clamp(DeltaTime * Grip->LerpSpeed, 0.f, 1.f); + } + + Alpha = FMath::Clamp(DeltaTime * Grip->LerpSpeed, 0.f, 1.f); + } + else + { + Grip->CurrentLerpTime -= DeltaTime * Grip->LerpSpeed; + float OrigAlpha = FMath::Clamp(1.0f - Grip->CurrentLerpTime, 0.f, 1.0f); + Alpha = OrigAlpha; + } + + FTransform NA = WorldTransform; + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + if (!bLerpRotationOnly) + { + WorldTransform.Blend(NB, NA, Alpha); + } + else + { + WorldTransform.SetRotation(FQuat::Slerp(NB.GetRotation(), NA.GetRotation(), Alpha)); + } + + if (bDistanceBasedInterpolation) + { + if(NA.Equals(WorldTransform, 0.01f)) + { + Grip->CurrentLerpTime = 0.0f; + } + else + { + // Save out current distance back to the originating transform + Grip->OnGripTransform = WorldTransform.GetRelativeTransform(this->GetPivotTransform()); + } + } + } + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + root->SetWorldTransform(WorldTransform, false);// , &OutHit); + } + else + { + root->SetWorldTransform(WorldTransform, false);// , &OutHit); + } + + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + } + + } + else if (Grip->bColliding) + { + if (!GripHandle) + { + SetUpPhysicsHandle(*Grip, &GripScripts); + } + else if (GripHandle->bIsPaused) + { + UnPausePhysicsHandle(*Grip, GripHandle); + } + + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + } + } + else + { + // Shouldn't be a grip handle if not server when server side moving + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + } + } + + }break; + + case EGripCollisionType::SweepWithPhysics: + { + // Ensure physics simulation is off in case something sneaked it on + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + + FVector OriginalPosition(root->GetComponentLocation()); + FRotator OriginalOrientation(root->GetComponentRotation()); + + FVector NewPosition(WorldTransform.GetTranslation()); + FRotator NewOrientation(WorldTransform.GetRotation()); + + root->ComponentVelocity = (NewPosition - OriginalPosition) / DeltaTime; + + // Now sweep collision separately so we can get hits but not have the location altered + if (bUseWithoutTracking || NewPosition != OriginalPosition || NewOrientation != OriginalOrientation) + { + FVector move = NewPosition - OriginalPosition; + + // ComponentSweepMulti does nothing if moving < UE_KINDA_SMALL_NUMBER in distance, so it's important to not try to sweep distances smaller than that. + const float MinMovementDistSq = (FMath::Square(4.f*UE_KINDA_SMALL_NUMBER)); + + if (bUseWithoutTracking || move.SizeSquared() > MinMovementDistSq || NewOrientation != OriginalOrientation) + { + if (CheckComponentWithSweep(root, move, OriginalOrientation, false)) + { + Grip->bColliding = true; + } + else + { + Grip->bColliding = false; + } + + TArray PrimChildren; + root->GetChildrenComponents(true, PrimChildren); + for (USceneComponent * Prim : PrimChildren) + { + if (UPrimitiveComponent * primComp = Cast(Prim)) + { + CheckComponentWithSweep(primComp, move, primComp->GetComponentRotation(), false); + } + } + } + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + else + { + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + + }break; + + case EGripCollisionType::PhysicsOnly: + { + // Ensure physics simulation is off in case something sneaked it on + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + else + { + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + }break; + + case EGripCollisionType::AttachmentGrip: + { + FTransform RelativeTrans = WorldTransform.GetRelativeTransform(ParentTransform); + + if (!root->GetAttachParent() || root->IsSimulatingPhysics()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Attachment Grip was missing attach parent - Attempting to Re-attach")); + + if (HasGripMovementAuthority(*Grip) || IsServer()) + { + root->SetSimulatePhysics(false); + if (root->AttachToComponent(IsValid(CustomPivotComponent) ? CustomPivotComponent.Get() : this, FAttachmentTransformRules::KeepWorldTransform)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Re-attached")); + if (!root->GetRelativeTransform().Equals(RelativeTrans)) + { + root->SetRelativeTransform(RelativeTrans); + } + } + } + } + else + { + if (!root->GetRelativeTransform().Equals(RelativeTrans)) + { + root->SetRelativeTransform(RelativeTrans); + } + } + + }break; + + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + }break; + + default: + {}break; + } + + // We only do this if specifically requested, it has a slight perf hit and isn't normally needed for non Custom Grip types + if (bAlwaysSendTickGrip) + { + // All non custom grips tick after translation, this is still pre physics so interactive grips location will be wrong, but others will be correct + if (bRootHasInterface) + { + IVRGripInterface::Execute_TickGrip(root, this, *Grip, DeltaTime); + } + + if (bActorHasInterface) + { + IVRGripInterface::Execute_TickGrip(actor, this, *Grip, DeltaTime); + } + } + } + else + { + // Object has been destroyed without notification to plugin or is pending kill + if (!Grip->bIsPendingKill) + { + CleanUpBadGrip(GrippedObjectsArray, i, bReplicatedArray); + } + } + } + } +} + + +void UGripMotionControllerComponent::CleanUpBadGrip(TArray &GrippedObjectsArray, int GripIndex, bool bReplicatedArray) +{ + // Object has been destroyed without notification to plugin + if (!DestroyPhysicsHandle(GrippedObjectsArray[GripIndex])) + { + // Clean up tailing physics handles with null objects + for (int g = PhysicsGrips.Num() - 1; g >= 0; --g) + { + if (!PhysicsGrips[g].HandledObject || PhysicsGrips[g].HandledObject == GrippedObjectsArray[GripIndex].GrippedObject || !IsValid(PhysicsGrips[g].HandledObject)) + { + // Need to delete it from the physics thread + DestroyPhysicsHandle(&PhysicsGrips[g]); + PhysicsGrips.RemoveAt(g); + } + } + } + + if (IsServer() || HasGripAuthority(GrippedObjectsArray[GripIndex])) + { + DropGrip_Implementation(GrippedObjectsArray[GripIndex], false); + UE_LOG(LogVRMotionController, Warning, TEXT("Gripped object was null or destroying, auto dropping it")); + } + else + { + GrippedObjectsArray[GripIndex].bIsPendingKill = true; + GrippedObjectsArray[GripIndex].bIsPaused = true; + } +} + +void UGripMotionControllerComponent::CleanUpBadPhysicsHandles() +{ + // Clean up tailing physics handles with null objects + for (int g = PhysicsGrips.Num() - 1; g >= 0; --g) + { + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(PhysicsGrips[g].GripID); + if(!GripInfo) + GrippedObjects.FindByKey(PhysicsGrips[g].GripID); + + if (!GripInfo) + { + // Need to delete it from the physics thread + DestroyPhysicsHandle(&PhysicsGrips[g]); + PhysicsGrips.RemoveAt(g); + } + } +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle(uint8 GripID, bool bFullyRecreate) +{ + FBPActorGripInformation* GripInfo = GrippedObjects.FindByKey(GripID); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripID); + + if (!GripInfo) + return false; + + return UpdatePhysicsHandle(*GripInfo, bFullyRecreate); +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle(const FBPActorGripInformation& GripInfo, bool bFullyRecreate) +{ + int HandleIndex = 0; + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(GripInfo); + + // Don't update if the handle doesn't exist or is currently paused + if (!HandleInfo || HandleInfo->bIsPaused || !HandleInfo->bInitiallySetup) + return false; + + if (bFullyRecreate || !HandleInfo->HandleData2.IsValid() || HandleInfo->bSkipResettingCom) + { + return SetUpPhysicsHandle(GripInfo); + } + + // Not fully recreating, we just re-set important variables + UPrimitiveComponent* root = GripInfo.GetGrippedComponent(); + AActor* pActor = GripInfo.GetGrippedActor(); + + if (!root && pActor) + root = Cast(pActor->GetRootComponent()); + + if (!root) + return false; + + FBodyInstance* rBodyInstance = root->GetBodyInstance(GripInfo.GrippedBoneName); + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance() || !FPhysicsInterface::IsValid(rBodyInstance->ActorHandle)) + { + return false; + } + + check(rBodyInstance->BodySetup->GetCollisionTraceFlag() != CTF_UseComplexAsSimple); + + FPhysicsCommand::ExecuteWrite(rBodyInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + if (HandleInfo) + { + if (HandleInfo->KinActorData2 && FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + // Make sure that the constraint particles are correct, the body instance may have changed + Chaos::FConstraintBase* ConstraintHandle = HandleInfo->HandleData2.Constraint; + if (ConstraintHandle) + { + ((Chaos::FJointConstraint*)ConstraintHandle)->SetParticleProxies({ HandleInfo->KinActorData2, Actor }); + } + + // Ensure center of mass is still correct + if (HandleInfo->bSetCOM && !HandleInfo->bSkipResettingCom) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + //localCom.SetLocation(Loc); + localCom.SetLocation(HandleInfo->COMPosition.GetTranslation());//Loc); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + } + } + } + }); + + return true; +//#endif + + return false; +} + +bool UGripMotionControllerComponent::PausePhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + HandleInfo->bIsPaused = true; + HandleInfo->bInitiallySetup = false; + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + return true; +} + +bool UGripMotionControllerComponent::UnPausePhysicsHandle(FBPActorGripInformation& GripInfo, FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + HandleInfo->bIsPaused = false; + SetUpPhysicsHandle(GripInfo); + + return true; +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + + if (!HandleInfo->bSkipDeletingKinematicActor) + { + if (FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + FPhysicsActorHandle ActorHandle = HandleInfo->KinActorData2; + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + FPhysicsInterface::ReleaseActor(HandleInfo->KinActorData2, FPhysicsInterface::GetCurrentScene(HandleInfo->KinActorData2)); + }); + } + } + + return true; +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle(const FBPActorGripInformation &Grip, bool bSkipUnregistering) +{ + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + { + return true; + } + + UPrimitiveComponent *root = Grip.GetGrippedComponent(); + AActor * pActor = Grip.GetGrippedActor(); + + if (!root && pActor) + root = Cast(pActor->GetRootComponent()); + + if (root) + { + if (FBodyInstance * rBodyInstance = root->GetBodyInstance(Grip.GrippedBoneName)) + { + // #TODO: Should this be done on drop instead? + // Remove event registration + if (!bSkipUnregistering) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + if (HandleInfo->bSetCOM) + { + // Reset center of mass to zero + // Get our original values + FVector vel = rBodyInstance->GetUnrealWorldVelocity(); + FVector aVel = rBodyInstance->GetUnrealWorldAngularVelocityInRadians(); + FVector originalCOM = rBodyInstance->GetCOMPosition(); + + if (rBodyInstance->IsValidBodyInstance() && rBodyInstance->BodySetup.IsValid()) + { + rBodyInstance->UpdateMassProperties(); + } + + if (rBodyInstance->IsInstanceSimulatingPhysics()) + { + // Offset the linear velocity by the new COM position and set it + vel += FVector::CrossProduct(aVel, rBodyInstance->GetCOMPosition() - originalCOM); + rBodyInstance->SetLinearVelocity(vel, false); + } + } + } + } + + DestroyPhysicsHandle(HandleInfo); + + int index; + if (GetPhysicsGripIndex(Grip, index)) + PhysicsGrips.RemoveAt(index); + + return true; +} + +void UGripMotionControllerComponent::OnGripMassUpdated(FBodyInstance* GripBodyInstance) +{ + TArray GripArray; + this->GetAllGrips(GripArray); + FBPActorGripInformation NewGrip; + + for (int i = 0; i < GripArray.Num(); i++) + { + NewGrip = GripArray[i]; + + UPrimitiveComponent *root = NewGrip.GetGrippedComponent(); + AActor * pActor = NewGrip.GetGrippedActor(); + + if (!root && pActor) + { + if (!IsValid(pActor)) + continue; + + root = Cast(pActor->GetRootComponent()); + } + + if (!root || root != GripBodyInstance->OwnerComponent) + continue; + + if (IsValid(root)) + { + UpdatePhysicsHandle(NewGrip, false); + } + break; + } +} + +bool UGripMotionControllerComponent::SetUpPhysicsHandle(const FBPActorGripInformation &NewGrip, TArray * GripScripts) +{ + UPrimitiveComponent *root = NewGrip.GetGrippedComponent(); + AActor * pActor = NewGrip.GetGrippedActor(); + + if(!root && pActor) + root = Cast(pActor->GetRootComponent()); + + if (!root) + return false; + + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(NewGrip); + if (HandleInfo == nullptr) + { + HandleInfo = CreatePhysicsGrip(NewGrip); + } + + // If currently paused lets skip this step + if (HandleInfo->bIsPaused) + { + return false; + } + + HandleInfo->bSetCOM = false; // Zero this out in case it is a re-init + HandleInfo->bSkipDeletingKinematicActor = (bConstrainToPivot && !NewGrip.bIsLerping); + + // Check for grip scripts if we weren't passed in any + TArray LocalGripScripts; + if (GripScripts == nullptr) + { + if (root && root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (IVRGripInterface::Execute_GetGripScripts(root, LocalGripScripts)) + { + GripScripts = &LocalGripScripts; + } + } + else if (pActor && pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (IVRGripInterface::Execute_GetGripScripts(pActor, LocalGripScripts)) + { + GripScripts = &LocalGripScripts; + } + } + } + + // Needs to be simulating in order to run physics + if (!NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewGrip.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + root->SetSimulatePhysics(true); + } + + // Get the PxRigidDynamic that we want to grab. + FBodyInstance* rBodyInstance = root->GetBodyInstance(NewGrip.GrippedBoneName); + Chaos::FPhysicsObject* PhysicsActor = root->GetPhysicsObjectByName(NewGrip.GrippedBoneName); + + bool bUseActorHandle = true; + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance() || !FPhysicsInterface::IsValid(rBodyInstance->ActorHandle) || !rBodyInstance->BodySetup.IsValid()) + { + if (PhysicsActor) + { + bUseActorHandle = false; + } + else + { + return false; + } + } + + if (rBodyInstance && bUseActorHandle) + { + check(rBodyInstance->BodySetup->GetCollisionTraceFlag() != CTF_UseComplexAsSimple); + + if (!HandleInfo->bSkipResettingCom && !FPhysicsInterface::IsValid(HandleInfo->KinActorData2) && !rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + // Reset the mass properties, this avoids an issue with some weird replication issues + // We only do this on initial grip + rBodyInstance->UpdateMassProperties(); + + } + } + + /*if (NewGrip.GrippedBoneName != NAME_None) + { + rBodyInstance->SetInstanceSimulatePhysics(true); + }*/ + + FTransform RootBoneRotation = FTransform::Identity; + if (NewGrip.GrippedBoneName != NAME_None) + { + // Skip root bone rotation + } + else + { + // I actually don't need any of this code anymore or the HandleInfo->RootBoneRotation + // However I would have to expect people to pass in the bone transform without it. + // For now I am keeping it to keep it backwards compatible as it will adjust for root bone rotation automatically then + if (USkeletalMeshComponent* skele = Cast(root)) + { + int32 RootBodyIndex = INDEX_NONE; + if (const UPhysicsAsset* PhysicsAsset = skele->GetPhysicsAsset()) + { + for (int32 i = 0; i < skele->GetNumBones(); i++) + { + if (PhysicsAsset->FindBodyIndex(skele->GetBoneName(i)) != INDEX_NONE) + { + RootBodyIndex = i; + break; + } + } + } + + if (RootBodyIndex != INDEX_NONE) + { + RootBoneRotation = FTransform(skele->GetBoneTransform(RootBodyIndex, FTransform::Identity)); + RootBoneRotation.SetScale3D(FVector(1.f)); + RootBoneRotation.NormalizeRotation(); + HandleInfo->RootBoneRotation = RootBoneRotation; + } + } + } + + // They forgot to make a single chaos::FPhysicsBody version of the write lock.... + bool bExecutedPhys = FPhysicsCommand::ExecuteWrite(PhysicsActor, PhysicsActor, [&](/*const FPhysicsActorHandle& Actor*/Chaos::FPhysicsObject* PhysActor, Chaos::FPhysicsObject* PhysActorNULL) + { + const FPhysicsActorHandle& Actor = bUseActorHandle ? rBodyInstance->GetPhysicsActorHandle() : nullptr; + Chaos::FWritePhysicsObjectInterface_External Interface = FPhysicsObjectExternalInterface::GetWrite_AssumesLocked(); + FChaosScene* PhysScene = PhysicsObjectPhysicsCoreInterface::GetScene({ &PhysActor, 1 }); + FTransform PhysActorTransform = Interface.GetTransform(PhysActor); + + if (!PhysScene) + { + return; + } + + FTransform KinPose; + FTransform trans = Interface.GetTransform(PhysActor);//FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor); + + EPhysicsGripCOMType COMType = NewGrip.AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings; + + if (!NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || COMType == EPhysicsGripCOMType::COM_Default) + { + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGrip || NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + COMType = EPhysicsGripCOMType::COM_GripAtControllerLoc; + } + else + { + COMType = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + // For geometry collections we can't set COM + if (!bUseActorHandle) + { + if (COMType == EPhysicsGripCOMType::COM_SetAndGripAt) + { + COMType = EPhysicsGripCOMType::COM_GripAtControllerLoc; + } + } + + if (bUseActorHandle && COMType == EPhysicsGripCOMType::COM_SetAndGripAt) + { + // Update the center of mass + FTransform ForwardTrans = (RootBoneRotation * NewGrip.RelativeTransform); + ForwardTrans.NormalizeRotation(); + FVector Loc = (FTransform(ForwardTrans.ToInverseMatrixWithScale())).GetLocation(); + Loc *= root->GetComponentScale(); + + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor);//Interface.GetWorldCoM(PhysActor); + localCom.SetLocation(Loc); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + + FVector ComLoc = FPhysicsInterface::GetComTransform_AssumesLocked(Actor).GetLocation(); // Interface.GetWorldCoM(PhysActor); + trans.SetLocation(ComLoc); + HandleInfo->COMPosition = FTransform(PhysActorTransform.InverseTransformPosition(ComLoc)); + HandleInfo->bSetCOM = true; + } + else if (COMType == EPhysicsGripCOMType::COM_GripAtControllerLoc) + { + FTransform ObjectTransform = PhysActorTransform; + ObjectTransform.SetScale3D(root->GetComponentScale()); + + FVector ControllerLoc = (FTransform(NewGrip.RelativeTransform.ToInverseMatrixWithScale()) * ObjectTransform).GetLocation(); + trans.SetLocation(ControllerLoc); + HandleInfo->COMPosition = FTransform(PhysActorTransform.InverseTransformPosition(ControllerLoc)); + } + else if (COMType != EPhysicsGripCOMType::COM_AtPivot) + { + FVector ComLoc = Interface.GetWorldCoM(PhysActor);// FPhysicsInterface::GetComTransform_AssumesLocked(Actor).GetLocation(); + trans.SetLocation(ComLoc); + HandleInfo->COMPosition = FTransform(PhysActorTransform.InverseTransformPosition(ComLoc)); + } + + KinPose = trans; + bool bRecreatingConstraint = false; + + + // If using twist only, lets rotate the kinematic actor to face the controller X+ + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + FTransform PivTrans = GetPivotTransform(); + + FQuat DeltaQuat = (PivTrans.GetRotation().Inverse() * KinPose.GetRotation()).Inverse(); + + // This moves the kinematic actor to face the pivot components X+ direction + KinPose.SetRotation(KinPose.GetRotation() * DeltaQuat); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation()* DeltaQuat); + } + + if (GripScripts) + { + bool bResetCom = false; + + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPrePhysicsHandle()) + { + Script->HandlePrePhysicsHandle(this, NewGrip, HandleInfo, KinPose); + bResetCom = true; + } + } + + if (bUseActorHandle && HandleInfo->bSetCOM && bResetCom) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + localCom.SetLocation(HandleInfo->COMPosition.GetTranslation());//Loc); + + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + } + } + + if (!NewGrip.bIsLerping && bConstrainToPivot && IsValid(CustomPivotComponent)) + { + if (UPrimitiveComponent* PivotPrim = Cast(CustomPivotComponent)) + { + if (FBodyInstance* Bodyinst = PivotPrim->GetBodyInstance(CustomPivotComponentSocketName)) + { + HandleInfo->KinActorData2 = Bodyinst->GetPhysicsActorHandle(); + } + } + } + + if (!FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + // Create kinematic actor we are going to create joint with. This will be moved around with calls to SetLocation/SetRotation. + + FActorCreationParams ActorParams; + ActorParams.InitialTM = KinPose; + ActorParams.DebugName = nullptr; + ActorParams.bEnableGravity = false; + ActorParams.bQueryOnly = false;// true; // True or false? + ActorParams.bStatic = false; + ActorParams.Scene = PhysScene;//FPhysicsInterface::GetCurrentScene(Actor); + FPhysicsInterface::CreateActor(ActorParams, HandleInfo->KinActorData2); + + if (FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + FPhysicsInterface::SetMass_AssumesLocked(HandleInfo->KinActorData2, 1.0f); + FPhysicsInterface::SetMassSpaceInertiaTensor_AssumesLocked(HandleInfo->KinActorData2, FVector(1.f)); + FPhysicsInterface::SetIsKinematic_AssumesLocked(HandleInfo->KinActorData2, true); + FPhysicsInterface::SetMaxDepenetrationVelocity_AssumesLocked(HandleInfo->KinActorData2, MAX_FLT); + //FPhysicsInterface::SetActorUserData_AssumesLocked(HandleInfo->KinActorData2, NULL); + } + + using namespace Chaos; + // Missing from physx, not sure how it is working for them currently. + //TArray ActorHandles; + HandleInfo->KinActorData2->GetGameThreadAPI().SetGeometry(TUniquePtr(new TSphere(TVector(0.f), 1000.f))); + HandleInfo->KinActorData2->GetGameThreadAPI().SetObjectState(EObjectStateType::Kinematic); + FPhysicsInterface::AddActorToSolver(HandleInfo->KinActorData2, ActorParams.Scene->GetSolver()); + //ActorHandles.Add(HandleInfo->KinActorData2); + //ActorParams.Scene->AddActorsToScene_AssumesLocked(ActorHandles); + } + + // If we don't already have a handle - make one now. + if (!HandleInfo->HandleData2.IsValid()) + { + // If this is true we will totally ignore the COM type, either we are gripping unstable or the com was set to our controller, we never accept + // Grip at COM + if (!NewGrip.bIsLerping && bConstrainToPivot) + { + FTransform TargetTrans(FTransform(NewGrip.RelativeTransform.ToMatrixNoScale().Inverse()) * HandleInfo->RootBoneRotation.Inverse()); + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2->GetPhysicsObject(), PhysActor, FTransform::Identity, TargetTrans); + } + else + { + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2->GetPhysicsObject(), PhysActor, FTransform::Identity, KinPose.GetRelativeTransform(PhysActorTransform)); + } + } + else + { + bRecreatingConstraint = true; + + /* + FTransform newTrans = HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform); + */ + + // There isn't a direct set for the particles, so keep it as a recreation instead. + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + + if (!NewGrip.bIsLerping && bConstrainToPivot) + { + FTransform TargetTrans(NewGrip.RelativeTransform.ToMatrixNoScale().Inverse()); + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2->GetPhysicsObject(), PhysActor, FTransform::Identity, TargetTrans); + } + else + { + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2->GetPhysicsObject(), PhysActor, FTransform::Identity, KinPose.GetRelativeTransform(PhysActorTransform)); + } + + } + + if (HandleInfo->HandleData2.IsValid()) + { + FPhysicsInterface::SetBreakForces_AssumesLocked(HandleInfo->HandleData2, MAX_FLT, MAX_FLT); + + + if (NewGrip.GripCollisionType == EGripCollisionType::LockedConstraint) + { + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::X, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Y, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Z, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Twist, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing1, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing2, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetProjectionEnabled_AssumesLocked(HandleInfo->HandleData2, true, 0.01f, 0.01f); + } + else + { + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::X, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Y, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Z, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Twist, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing1, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing2, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetProjectionEnabled_AssumesLocked(HandleInfo->HandleData2, false); + } + + FPhysicsInterface::SetDrivePosition(HandleInfo->HandleData2, FVector::ZeroVector); + + bool bUseForceDrive = (NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.PhysicsConstraintType == EPhysicsGripConstraintType::ForceConstraint); + + float Stiffness = NewGrip.Stiffness; + float Damping = NewGrip.Damping; + float MaxForce; + float AngularStiffness; + float AngularDamping; + float AngularMaxForce; + + const UVRGlobalSettings& VRSettings = *GetDefault(); + + if (NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + AngularStiffness = NewGrip.AdvancedGripSettings.PhysicsSettings.AngularStiffness; + AngularDamping = NewGrip.AdvancedGripSettings.PhysicsSettings.AngularDamping; + } + else + { + AngularStiffness = Stiffness * ANGULAR_STIFFNESS_MULTIPLIER; // Default multiplier + AngularDamping = Damping * ANGULAR_DAMPING_MULTIPLIER; // Default multiplier + + if (!VRSettings.bUseChaosTranslationScalers) + { + AngularStiffness *= ANGULAR_STIFFNESS_MULTIPLIER_CHAOS; + AngularDamping *= ANGULAR_DAMPING_MULTIPLIER_CHAOS; + } + } + + if (VRSettings.bUseChaosTranslationScalers) + { + Stiffness *= VRSettings.LinearDriveStiffnessScale; + Damping *= VRSettings.LinearDriveDampingScale; + AngularStiffness *= VRSettings.AngularDriveStiffnessScale; + AngularDamping *= VRSettings.AngularDriveDampingScale; + } + else + { + auto CVarLinearDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearDriveStiffnessScale")); + auto CVarLinearDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinaearDriveDampingScale")); + auto CVarAngularDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveStiffnessScale")); + auto CVarAngularDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveDampingScale")); + + Stiffness *= CVarLinearDriveStiffnessScale->GetFloat(); + Damping *= CVarLinearDriveDampingScale->GetFloat(); + AngularStiffness *= CVarAngularDriveStiffnessScale->GetFloat(); + AngularDamping *= CVarAngularDriveDampingScale->GetFloat(); + } + + AngularMaxForce = (float)FMath::Clamp((double)AngularStiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp((double)Stiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + + // Different settings for manip grip + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGrip || NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + if (!bRecreatingConstraint) + { + FConstraintDrive NewLinDrive; + NewLinDrive.bEnablePositionDrive = true; + NewLinDrive.bEnableVelocityDrive = true; + NewLinDrive.Damping = Damping; + NewLinDrive.Stiffness = Stiffness; + NewLinDrive.MaxForce = MaxForce; + + HandleInfo->LinConstraint.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive = NewLinDrive; + HandleInfo->LinConstraint.YDrive = NewLinDrive; + HandleInfo->LinConstraint.ZDrive = NewLinDrive; + } + + + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + if (!bRecreatingConstraint) + { + FConstraintDrive NewAngDrive; + NewAngDrive.bEnablePositionDrive = true; + NewAngDrive.bEnableVelocityDrive = true; + NewAngDrive.Damping = AngularDamping; + NewAngDrive.Stiffness = AngularStiffness; + NewAngDrive.MaxForce = AngularMaxForce; + //NewAngDrive.MaxForce = MAX_FLT; + + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::TwistAndSwing; + //AngParams.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.TwistDrive = NewAngDrive; + } + } + + if (GripScripts) + { + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPostPhysicsHandle()) + { + Script->HandlePostPhysicsHandle(this, HandleInfo); + } + } + } + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + else + { + if (NewGrip.GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithPhysics) + { + // Do not effect damping, just increase stiffness so that it is stronger + // Default multiplier + Stiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularStiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularMaxForce = (float)FMath::Clamp((double)AngularStiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp((double)Stiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + } + + if (!bRecreatingConstraint) + { + if (NewGrip.GripCollisionType != EGripCollisionType::LockedConstraint) + { + FConstraintDrive NewLinDrive; + NewLinDrive.bEnablePositionDrive = true; + NewLinDrive.bEnableVelocityDrive = true; + NewLinDrive.Damping = Damping; + NewLinDrive.Stiffness = Stiffness; + NewLinDrive.MaxForce = MaxForce; + //NewLinDrive.MaxForce = MAX_FLT; + + FConstraintDrive NewAngDrive; + NewAngDrive.bEnablePositionDrive = true; + NewAngDrive.bEnableVelocityDrive = true; + NewAngDrive.Damping = AngularDamping; + NewAngDrive.Stiffness = AngularStiffness; + NewAngDrive.MaxForce = AngularMaxForce; + //NewAngDrive.MaxForce = MAX_FLT; + + HandleInfo->LinConstraint.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive = NewLinDrive; + HandleInfo->LinConstraint.YDrive = NewLinDrive; + HandleInfo->LinConstraint.ZDrive = NewLinDrive; + + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.SlerpDrive = NewAngDrive; + } + } + + if (GripScripts) + { + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPostPhysicsHandle()) + { + Script->HandlePostPhysicsHandle(this, HandleInfo); + } + } + } + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + + + // This is a temp workaround until epic fixes the drive creation to allow force constraints + // I wanted to use the new interface and not directly set the drive so that it is ready to delete this section + // When its fixed + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } + } + }); + + if (!bExecutedPhys) + { + return false; + } + + HandleInfo->bInitiallySetup = true; + + // Bind to further updates in order to keep it alive + if (bUseActorHandle && !rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().AddUObject(this, &UGripMotionControllerComponent::OnGripMassUpdated); + } + + return true; +} + +bool UGripMotionControllerComponent::SetGripConstraintStiffnessAndDamping(const FBPActorGripInformation* Grip, bool bUseHybridMultiplier) +{ + if (!Grip) + return false; + + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(*Grip); + + if (HandleInfo) + { + if (HandleInfo->HandleData2.IsValid()) + { + FPhysicsInterface::ExecuteOnUnbrokenConstraintReadWrite(HandleInfo->HandleData2, [&](const FPhysicsConstraintHandle& InUnbrokenConstraint) + { + bool bUseForceDrive = (Grip->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && Grip->AdvancedGripSettings.PhysicsSettings.PhysicsConstraintType == EPhysicsGripConstraintType::ForceConstraint); + + float Stiffness = Grip->Stiffness; + float Damping = Grip->Damping; + float MaxForce; + float AngularStiffness; + float AngularDamping; + float AngularMaxForce; + + const UVRGlobalSettings& VRSettings = *GetDefault(); + + if (Grip->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && Grip->AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + AngularStiffness = Grip->AdvancedGripSettings.PhysicsSettings.AngularStiffness; + AngularDamping = Grip->AdvancedGripSettings.PhysicsSettings.AngularDamping; + } + else + { + AngularStiffness = Stiffness * ANGULAR_STIFFNESS_MULTIPLIER; // Default multiplier + AngularDamping = Damping * ANGULAR_DAMPING_MULTIPLIER; // Default multiplier + + if (!VRSettings.bUseChaosTranslationScalers) + { + AngularStiffness *= ANGULAR_STIFFNESS_MULTIPLIER_CHAOS; + AngularDamping *= ANGULAR_DAMPING_MULTIPLIER_CHAOS; + } + } + + if (VRSettings.bUseChaosTranslationScalers) + { + Stiffness *= VRSettings.LinearDriveStiffnessScale; + Damping *= VRSettings.LinearDriveDampingScale; + AngularStiffness *= VRSettings.AngularDriveStiffnessScale; + AngularDamping *= VRSettings.AngularDriveDampingScale; + } + else + { + if (VRSettings.bUseChaosTranslationScalers) + { + Stiffness *= VRSettings.LinearDriveStiffnessScale; + Damping *= VRSettings.LinearDriveDampingScale; + AngularStiffness *= VRSettings.AngularDriveStiffnessScale; + AngularDamping *= VRSettings.AngularDriveDampingScale; + } + else + { + auto CVarLinearDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearDriveStiffnessScale")); + auto CVarLinearDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinaearDriveDampingScale")); + auto CVarAngularDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveStiffnessScale")); + auto CVarAngularDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveDampingScale")); + + Stiffness *= CVarLinearDriveStiffnessScale->GetFloat(); + Damping *= CVarLinearDriveDampingScale->GetFloat(); + AngularStiffness *= CVarAngularDriveStiffnessScale->GetFloat(); + AngularDamping *= CVarAngularDriveDampingScale->GetFloat(); + } + } + + AngularMaxForce = (float)FMath::Clamp((double)AngularStiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp((double)Stiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + + + // Different settings for manip grip + if (Grip->GripCollisionType == EGripCollisionType::ManipulationGrip || Grip->GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + HandleInfo->LinConstraint.XDrive.Damping = Damping; + HandleInfo->LinConstraint.XDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.XDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.YDrive.Damping = Damping; + HandleInfo->LinConstraint.YDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.YDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.ZDrive.Damping = Damping; + HandleInfo->LinConstraint.ZDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.ZDrive.MaxForce = MaxForce; + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } + + if (Grip->GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + HandleInfo->AngConstraint.TwistDrive.Damping = AngularDamping; + HandleInfo->AngConstraint.TwistDrive.Stiffness = AngularStiffness; + HandleInfo->AngConstraint.TwistDrive.MaxForce = AngularMaxForce; + + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + + FPhysicsInterface::SetDrivePosition(HandleInfo->HandleData2, FVector::ZeroVector); + FPhysicsInterface::SetDriveOrientation(HandleInfo->HandleData2, FQuat::Identity); + } + else + { + if (Grip->GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithPhysics) + { + // Do not effect damping, just increase stiffness so that it is stronger + // Default multiplier + Stiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularStiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + + AngularMaxForce = (float)FMath::Clamp((double)AngularStiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp((double)Stiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + } + + HandleInfo->LinConstraint.XDrive.Damping = Damping; + HandleInfo->LinConstraint.XDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.XDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.YDrive.Damping = Damping; + HandleInfo->LinConstraint.YDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.YDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.ZDrive.Damping = Damping; + HandleInfo->LinConstraint.ZDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.ZDrive.MaxForce = MaxForce; + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } + + + HandleInfo->AngConstraint.SlerpDrive.Damping = AngularDamping; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = AngularStiffness; + HandleInfo->AngConstraint.SlerpDrive.MaxForce = AngularMaxForce; + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } + } + + }); + + } + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::GetPhysicsJointLength(const FBPActorGripInformation &GrippedActor, UPrimitiveComponent * rootComp, FVector & LocOut) +{ + if (!GrippedActor.GrippedObject) + return false; + + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(GrippedActor); + + if (!HandleInfo || !FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + return false; + + if (!HandleInfo->HandleData2.IsValid()) + return false; + + // Not using com with skipping mass check as the COM can change on us + // Also skipping it on skip resetting com as we aren't gripped to the COM then + bool bUseComLoc = + ( + !HandleInfo->bSkipResettingCom && + (HandleInfo->bSetCOM || + (GrippedActor.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && GrippedActor.AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings == EPhysicsGripCOMType::COM_GripAt)) + ); + + // This is supposed to be the difference between the actor and the kinactor / constraint base + FTransform tran3 = FTransform::Identity; + + FBodyInstance* rBodyInstance = rootComp->GetBodyInstance(GrippedActor.GrippedBoneName); + + if (bUseComLoc && rBodyInstance && rBodyInstance->IsValidBodyInstance()) + { + tran3 = FTransform(rBodyInstance->GetCOMPosition()); + } + else + { + FTransform rr; + tran3 = FPhysicsInterface::GetLocalPose(HandleInfo->HandleData2, EConstraintFrame::Frame2); + + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance()) + { + rr = rootComp->GetComponentTransform(); + // Physx location throws out scale, this is where the problem was + rr.SetScale3D(FVector(1, 1, 1)); + } + else + rr = rBodyInstance->GetUnrealWorldTransform(); + + // Make the local pose global + tran3 = tran3 * rr; + } + + // Get the global pose for the kin actor + FTransform kinPose = FTransform::Identity; + FPhysicsCommand::ExecuteRead(HandleInfo->KinActorData2, [&](const FPhysicsActorHandle & Actor) + { + kinPose = FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor); + }); + + // Return the difference + LocOut = FTransform::SubtractTranslations(kinPose, tran3); + + return true; +} + +void UGripMotionControllerComponent::UpdatePhysicsHandleTransform(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform) +{ + if (!GrippedActor.GrippedObject || (bConstrainToPivot && !GrippedActor.bIsLerping) || IsTravelingOrNullWorld()) + return; + + if (!NewTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, UpdatePhysicsHandeTransforms target transform contained NAN!.")); + return; + } + + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(GrippedActor); + + if (!HandleInfo || !FPhysicsInterface::IsValid(HandleInfo->KinActorData2))// || !HandleInfo->HandleData2.IsValid()) + return; + + // Don't call moveKinematic if it hasn't changed - that will stop bodies from going to sleep. + if (!HandleInfo->LastPhysicsTransform.EqualsNoScale(NewTransform)) + { + HandleInfo->LastPhysicsTransform = NewTransform; + HandleInfo->LastPhysicsTransform.SetScale3D(FVector(1.0f)); + FPhysicsActorHandle ActorHandle = HandleInfo->KinActorData2; + FTransform newTrans = HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform); + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle & Actor) + { + FPhysicsInterface::SetKinematicTarget_AssumesLocked(Actor, newTrans); + }); + } + + // Debug draw for COM movement with physics grips +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (GripMotionControllerCvars::DrawDebugGripCOM) + { + UPrimitiveComponent* me = Cast(GrippedActor.GripTargetType == EGripTargetType::ActorGrip ? GrippedActor.GetGrippedActor()->GetRootComponent() : GrippedActor.GetGrippedComponent()); + FVector curCOMPosition = me->GetBodyInstance(GrippedActor.GrippedBoneName)->GetCOMPosition(); + DrawDebugSphere(GetWorld(), curCOMPosition, 4, 32, FColor::Red, false); + FTransform TargetTransform = (HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform)); + DrawDebugSphere(GetWorld(), TargetTransform.GetLocation(), 4, 32, FColor::Cyan, false); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetForwardVector() * 20.f), FColor::Red); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetRightVector() * 20.f), FColor::Green); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetUpVector() * 20.f), FColor::Blue); + } +#endif + +} + +static void PullBackHitComp(FHitResult& Hit, const FVector& Start, const FVector& End, const float Dist) +{ + const float DesiredTimeBack = FMath::Clamp(0.1f, 0.1f / Dist, 1.f / Dist) + 0.001f; + Hit.Time = FMath::Clamp(Hit.Time - DesiredTimeBack, 0.f, 1.f); +} + +bool UGripMotionControllerComponent::CheckComponentWithSweep(UPrimitiveComponent * ComponentToCheck, FVector Move, FRotator newOrientation, bool bSkipSimulatingComponents/*, bool &bHadBlockingHitOut*/) +{ + TArray Hits; + // WARNING: HitResult is only partially initialized in some paths. All data is valid only if bFilledHitResult is true. + FHitResult BlockingHit(NoInit); + BlockingHit.bBlockingHit = false; + BlockingHit.Time = 1.f; + bool bFilledHitResult = false; + bool bMoved = false; + bool bIncludesOverlapsAtEnd = false; + bool bRotationOnly = false; + + UPrimitiveComponent *root = ComponentToCheck; + + if (!root || !root->IsQueryCollisionEnabled()) + return false; + + FVector start(root->GetComponentLocation()); + + const bool bCollisionEnabled = root->IsQueryCollisionEnabled(); + + if (bCollisionEnabled) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (!root->IsRegistered()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("MovedComponent %s not initialized in grip motion controller"), *root->GetFullName()); + } +#endif + + UWorld* const MyWorld = GetWorld(); + FComponentQueryParams Params(TEXT("sweep_params"), root->GetOwner()); + + FCollisionResponseParams ResponseParam; + root->InitSweepCollisionParams(Params, ResponseParam); + + FVector end = start + Move; + bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, root, start, end, newOrientation.Quaternion(), Params); + + if (Hits.Num() > 0) + { + const float DeltaSize = FVector::Dist(start, end); + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + PullBackHitComp(Hits[HitIdx], start, end, DeltaSize); + } + } + + if (bHadBlockingHit) + { + int32 BlockingHitIndex = INDEX_NONE; + float BlockingHitNormalDotDelta = UE_BIG_NUMBER; + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + const FHitResult& TestHit = Hits[HitIdx]; + + // Ignore the owning actor to the motion controller + if (TestHit.GetActor() == this->GetOwner() || (bSkipSimulatingComponents && TestHit.Component->IsSimulatingPhysics())) + { + if (Hits.Num() == 1) + { + //bHadBlockingHitOut = false; + return false; + } + else + continue; + } + + if (TestHit.bBlockingHit && TestHit.IsValidBlockingHit()) + { + if (TestHit.Time == 0.f) + { + // We may have multiple initial hits, and want to choose the one with the normal most opposed to our movement. + const float NormalDotDelta = (TestHit.ImpactNormal | Move); + if (NormalDotDelta < BlockingHitNormalDotDelta) + { + BlockingHitNormalDotDelta = NormalDotDelta; + BlockingHitIndex = HitIdx; + } + } + else if (BlockingHitIndex == INDEX_NONE) + { + // First non-overlapping blocking hit should be used, if an overlapping hit was not. + // This should be the only non-overlapping blocking hit, and last in the results. + BlockingHitIndex = HitIdx; + break; + } + //} + } + } + + // Update blocking hit, if there was a valid one. + if (BlockingHitIndex >= 0) + { + BlockingHit = Hits[BlockingHitIndex]; + bFilledHitResult = true; + } + } + } + + // Handle blocking hit notifications. Avoid if pending kill (which could happen after overlaps). + if (BlockingHit.bBlockingHit && IsValid(root)) + { + check(bFilledHitResult); + if (root->IsDeferringMovementUpdates()) + { + FScopedMovementUpdate* ScopedUpdate = root->GetCurrentScopedMovement(); + ScopedUpdate->AppendBlockingHitAfterMove(BlockingHit); + } + else + { + + if(root->GetOwner()) + root->DispatchBlockingHit(*root->GetOwner(), BlockingHit); + } + + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::HasTrackingParameters() +{ + return /*bOffsetByHMD ||*/ bScaleTracking || bLeashToHMD || bLimitMinHeight || bLimitMaxHeight || (AttachChar && !AttachChar->bRetainRoomscale); +} + +void UGripMotionControllerComponent::ApplyTrackingParameters(FVector& OriginalPosition, bool bIsInGameThread, bool bApplyZeroing) +{ + if (bScaleTracking) + { + OriginalPosition *= TrackingScaler; + } + + if (bLimitMinHeight) + { + OriginalPosition.Z = FMath::Max(OriginalPosition.Z, MinimumHeight); + } + + if (bLimitMaxHeight) + { + OriginalPosition.Z = FMath::Min(OriginalPosition.Z, MaximumHeight); + } + + if (bApplyZeroing && (/*bOffsetByHMD ||*/ bLeashToHMD || (AttachChar && !AttachChar->bRetainRoomscale))) + { + if (bIsInGameThread) + { + if (IsLocallyControlled() && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot; + FVector curLoc; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curLoc)) + { + + if (IsValid(AttachChar) && AttachChar->VRReplicatedCamera) + { + AttachChar->VRReplicatedCamera->ApplyTrackingParameters(curLoc, true); + } + + //curLoc.Z = 0; + LastLocationForLateUpdate = curLoc; + + if (IsValid(AttachChar) && !AttachChar->bRetainRoomscale) + { + FRotator StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curRot.Rotator()); + LastLocationForLateUpdate += StoredCameraRotOffset.RotateVector(FVector(AttachChar->VRRootReference->VRCapsuleOffset.X, AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + } + } + else + { + if (IsValid(AttachChar) && AttachChar->VRReplicatedCamera) + { + // Sample camera location instead + LastLocationForLateUpdate = AttachChar->VRReplicatedCamera->GetRelativeLocation(); + + if (!AttachChar->bRetainRoomscale && IsLocallyControlled()) + { + FRotator StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(AttachChar->VRReplicatedCamera->GetRelativeRotation()); + LastLocationForLateUpdate += StoredCameraRotOffset.RotateVector(FVector(AttachChar->VRRootReference->VRCapsuleOffset.X, AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + } + } + } + + // #TODO: This is technically unsafe, need to use a seperate value like the transforms for the render thread + // If I ever delete the simple char then this setup can just go away anyway though + // It has a data race condition right now though + FVector CorrectLastLocation = bIsInGameThread ? LastLocationForLateUpdate : LateUpdateParams.GripRenderThreadLastLocationForLateUpdate; + + if (bLeashToHMD) + { + FVector DifferenceVec = OriginalPosition - CorrectLastLocation; + + if (DifferenceVec.SizeSquared() > FMath::Square(LeashRange)) + { + OriginalPosition = CorrectLastLocation + (DifferenceVec.GetSafeNormal() * LeashRange); + } + } + + if (/*bOffsetByHMD ||*/ (AttachChar && !AttachChar->bRetainRoomscale)) + { + OriginalPosition -= FVector(CorrectLastLocation.X, CorrectLastLocation.Y, 0.0f); + } + } +} + +void UGripMotionControllerComponent::OnModularFeatureUnregistered(const FName& Type, class IModularFeature* ModularFeature) +{ + FScopeLock Lock(&PolledMotionControllerMutex); + + if (ModularFeature == PolledMotionController_GameThread) + { + PolledMotionController_GameThread = nullptr; + } + if (ModularFeature == PolledMotionController_RenderThread) + { + PolledMotionController_RenderThread = nullptr; + } +} + + +//============================================================================= +bool UGripMotionControllerComponent::GripPollControllerState(FVector& Position, FRotator& Orientation, float WorldToMetersScale) +{ + if (IsInGameThread()) + { + bool OutbProvidedLinearVelocity; + bool OutbProvidedAngularVelocity; + bool OutbProvidedLinearAcceleration; + FVector OutLinearVelocity; + FVector OutAngularVelocityAsAxisAndLength; + FVector OutLinearAcceleration; + return GripPollControllerState_GameThread(Position, Orientation, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale); + } + else + { + return GripPollControllerState_RenderThread(Position, Orientation, WorldToMetersScale); + } +} + +bool UGripMotionControllerComponent::GripPollControllerState_GameThread(FVector& Position, FRotator& Orientation, bool& OutbProvidedLinearVelocity, FVector& OutLinearVelocity, bool& OutbProvidedAngularVelocity, FVector& OutAngularVelocityAsAxisAndLength, bool& OutbProvidedLinearAcceleration, FVector& OutLinearAcceleration, float WorldToMetersScale) +{ + // Not calling PollControllerState from the parent because its private....... + + bool bIsInGameThread = true; + + if (bHasAuthority) + { + { + FScopeLock Lock(&PolledMotionControllerMutex); + PolledMotionController_GameThread = nullptr; + bPolledHMD_GameThread = false; + } + + //GripUEMotionController::FScopeLockOptional LockOptional; + TArray MotionControllers; + MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations(IMotionController::GetModularFeatureName()); + for (auto MotionController : MotionControllers) + { + if (MotionController == nullptr) + { + continue; + } + + if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + if (!bIgnoreTrackingStatus && CurrentTrackingStatus == ETrackingStatus::NotTracked) + continue; + } + + if (MotionController->GetControllerOrientationAndPosition(PlayerIndex, MotionSource, Orientation, Position, OutbProvidedLinearVelocity, OutLinearVelocity, OutbProvidedAngularVelocity, OutAngularVelocityAsAxisAndLength, OutbProvidedLinearAcceleration, OutLinearAcceleration, WorldToMetersScale)) + { + /*#if PLATFORM_PS4 + // Moving this in here to work around a PSVR module bug + if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + if (CurrentTrackingStatus == ETrackingStatus::NotTracked) + continue; + } + #endif*/ + + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position, bIsInGameThread); + } + + if (bOffsetByControllerProfile) + { + FTransform FinalControllerTransform(Orientation,Position); + if (bIsInGameThread) + { + FinalControllerTransform = CurrentControllerProfileTransform * FinalControllerTransform; + } + else + { + FinalControllerTransform = LateUpdateParams.GripRenderThreadProfileTransform * FinalControllerTransform; + } + + Orientation = FinalControllerTransform.Rotator(); + Position = FinalControllerTransform.GetTranslation(); + } + + InUseMotionController = MotionController; + OnMotionControllerUpdated(); + InUseMotionController = nullptr; + + { + FScopeLock Lock(&PolledMotionControllerMutex); + PolledMotionController_GameThread = MotionController; // We only want a render thread update from the motion controller we polled on the game thread. + } + return true; + } + + /*#if PLATFORM_PS4 + else if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + } + #endif*/ + } + + // #NOTE: This was adding in 4.20, I presume to allow for HMDs as tracking sources for mixed reality. + // Skipping all of my special logic here for now + if (MotionSource == IMotionController::HMDSourceId) + { + IXRTrackingSystem* TrackingSys = GEngine->XRSystem.Get(); + if (TrackingSys) + { + FQuat OrientationQuat; + if (TrackingSys->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, OrientationQuat, Position)) + { + Orientation = OrientationQuat.Rotator(); + { + FScopeLock Lock(&PolledMotionControllerMutex); + bPolledHMD_GameThread = true; // We only want a render thread update from the hmd if we polled it on the game thread. + } + return true; + } + } + } + } + return false; +} + +bool UGripMotionControllerComponent::GripPollControllerState_RenderThread(FVector& Position, FRotator& Orientation, float WorldToMetersScale) +{ + check(IsInRenderingThread()); + bool bIsInGameThread = false; + + if (PolledMotionController_RenderThread) + { + CurrentTrackingStatus = PolledMotionController_RenderThread->GetControllerTrackingStatus(PlayerIndex, MotionSource); + if (PolledMotionController_RenderThread->GetControllerOrientationAndPosition(PlayerIndex, MotionSource, Orientation, Position, WorldToMetersScale)) + { + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position, bIsInGameThread); + } + + if (bOffsetByControllerProfile) + { + FTransform FinalControllerTransform(Orientation, Position); + if (bIsInGameThread) + { + FinalControllerTransform = CurrentControllerProfileTransform * FinalControllerTransform; + } + else + { + FinalControllerTransform = LateUpdateParams.GripRenderThreadProfileTransform * FinalControllerTransform; + } + + Orientation = FinalControllerTransform.Rotator(); + Position = FinalControllerTransform.GetTranslation(); + } + return true; + } + } + + if (bPolledHMD_RenderThread) + { + IXRTrackingSystem* TrackingSys = GEngine->XRSystem.Get(); + if (TrackingSys) + { + FQuat OrientationQuat; + if (TrackingSys->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, OrientationQuat, Position)) + { + Orientation = OrientationQuat.Rotator(); + return true; + } + } + } + + return false; +} + +//============================================================================= +UGripMotionControllerComponent::FGripViewExtension::FGripViewExtension(const FAutoRegister& AutoRegister, UGripMotionControllerComponent* InMotionControllerComponent) + : FSceneViewExtensionBase(AutoRegister) + , MotionControllerComponent(InMotionControllerComponent) +{ + FSceneViewExtensionIsActiveFunctor IsActiveFunc; + IsActiveFunc.IsActiveFunction = [this](const ISceneViewExtension* SceneViewExtension, const FSceneViewExtensionContext& Context) + { + check(IsInGameThread()); + + static const auto CVarEnableMotionControllerLateUpdate = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.EnableMotionControllerLateUpdate")); + return MotionControllerComponent && !MotionControllerComponent->bDisableLowLatencyUpdate && MotionControllerComponent->bTracked && CVarEnableMotionControllerLateUpdate->GetValueOnGameThread(); + }; + + IsActiveThisFrameFunctions.Add(IsActiveFunc); +} + + +void UGripMotionControllerComponent::FGripViewExtension::PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) +{ + FTransform OldTransform; + FTransform NewTransform; + + { + FScopeLock ScopeLock(&CritSect); + + if (!MotionControllerComponent) + return; + + { + FScopeLock Lock(&MotionControllerComponent->PolledMotionControllerMutex); + MotionControllerComponent->PolledMotionController_RenderThread = MotionControllerComponent->PolledMotionController_GameThread; + } + + // Find a view that is associated with this player. + float WorldToMetersScale = -1.0f; + for (const FSceneView* SceneView : InViewFamily.Views) + { + if (SceneView && SceneView->PlayerIndex == MotionControllerComponent->PlayerIndex) + { + WorldToMetersScale = SceneView->WorldToMetersScale; + break; + } + } + + // If there are no views associated with this player use view 0. + if (WorldToMetersScale < 0.0f) + { + check(InViewFamily.Views.Num() > 0); + WorldToMetersScale = InViewFamily.Views[0]->WorldToMetersScale; + } + + // Poll state for the most recent controller transform + FVector Position = MotionControllerComponent->LateUpdateParams.GripRenderThreadRelativeTransform.GetTranslation(); + FRotator Orientation = MotionControllerComponent->LateUpdateParams.GripRenderThreadRelativeTransform.GetRotation().Rotator(); + + if (!MotionControllerComponent->PollControllerState_RenderThread(Position, Orientation, WorldToMetersScale)) + { + return; + } + + if (MotionControllerComponent->LateUpdateParams.bRenderSmoothHandTracking) + { + FTransform CalcedTransform = FTransform(Orientation, Position, MotionControllerComponent->LateUpdateParams.GripRenderThreadComponentScale); + + if (MotionControllerComponent->LateUpdateParams.bRenderSmoothWithEuroLowPassFunction) + { + CalcedTransform = MotionControllerComponent->LateUpdateParams.RenderEuroSmoothingParams.RunFilterSmoothing(CalcedTransform, MotionControllerComponent->LateUpdateParams.RenderLastDeltaTime); + //SetRelativeTransform(RenderEuroSmoothingParams.RunFilterSmoothing(CalcedTransform, RenderLastDeltaTime)); + } + else + { + if (MotionControllerComponent->LateUpdateParams.RenderSmoothingSpeed <= 0.f || MotionControllerComponent->LateUpdateParams.RenderLastSmoothRelativeTransform.Equals(FTransform::Identity)) + { + //SetRelativeTransform(CalcedTransform); + } + else + { + const float Alpha = FMath::Clamp(MotionControllerComponent->LateUpdateParams.RenderLastDeltaTime * MotionControllerComponent->LateUpdateParams.RenderSmoothingSpeed, 0.f, 1.f); + MotionControllerComponent->LateUpdateParams.RenderLastSmoothRelativeTransform.Blend(MotionControllerComponent->LateUpdateParams.RenderLastSmoothRelativeTransform, CalcedTransform, Alpha); + CalcedTransform = MotionControllerComponent->LateUpdateParams.RenderLastSmoothRelativeTransform; + //SetRelativeTransform(LastSmoothRelativeTransform); + } + } + + // Set smoothed properties + NewTransform = CalcedTransform; + } + else + { + NewTransform = FTransform(Orientation, Position, MotionControllerComponent->LateUpdateParams.GripRenderThreadComponentScale); + } + + OldTransform = MotionControllerComponent->LateUpdateParams.GripRenderThreadRelativeTransform; + //NewTransform = FTransform(Orientation, Position, MotionControllerComponent->GripRenderThreadComponentScale); + MotionControllerComponent->LateUpdateParams.GripRenderThreadRelativeTransform = NewTransform; + } // Release lock on motion controller component + + // Tell the late update manager to apply the offset to the scene components + LateUpdate.Apply_RenderThread(InViewFamily.Scene, OldTransform, NewTransform); + // #TODO: UE5 is missing this pull + //LateUpdate.Apply_RenderThread(InViewFamily.Scene, InViewFamily.bLateLatchingEnabled ? InViewFamily.FrameNumber : -1, OldTransform, NewTransform); +} + +/*void UGripMotionControllerComponent::FGripViewExtension::LateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) +{ + SCOPED_NAMED_EVENT(UMotionControllerComponent_Latch, FColor::Orange); + if (!MotionControllerComponent) + { + return; + } + + FTransform OldTransform; + FTransform NewTransform; + { + FScopeLock ScopeLock(&CritSect); + if (!MotionControllerComponent) + { + return; + } + + // Find a view that is associated with this player. + float WorldToMetersScale = -1.0f; + for (const FSceneView* SceneView : InViewFamily.Views) + { + if (SceneView && SceneView->PlayerIndex == MotionControllerComponent->PlayerIndex) + { + WorldToMetersScale = SceneView->WorldToMetersScale; + break; + } + } + // If there are no views associated with this player use view 0. + if (WorldToMetersScale < 0.0f) + { + check(InViewFamily.Views.Num() > 0); + WorldToMetersScale = InViewFamily.Views[0]->WorldToMetersScale; + } + + // Poll state for the most recent controller transform + FVector Position; + FRotator Orientation; + + if (!MotionControllerComponent->GripPollControllerState(Position, Orientation, WorldToMetersScale)) + { + return; + } + + OldTransform = MotionControllerComponent->GripRenderThreadRelativeTransform; + NewTransform = FTransform(Orientation, Position, MotionControllerComponent->GripRenderThreadComponentScale); + MotionControllerComponent->GripRenderThreadRelativeTransform = NewTransform; + + } // Release the lock on the MotionControllerComponent + + // Tell the late update manager to apply the offset to the scene components + LateUpdate.Apply_RenderThread(InViewFamily.Scene, InViewFamily.FrameNumber, OldTransform, NewTransform); +}*/ + +bool UGripMotionControllerComponent::K2_GetFirstActiveGrip(FBPActorGripInformation& FirstActiveGrip) +{ + FBPActorGripInformation* FirstGrip = GetFirstActiveGrip(); + + if (FirstGrip) + { + FirstActiveGrip = *FirstGrip; + return true; + } + + return false; +} + +FBPActorGripInformation* UGripMotionControllerComponent::GetFirstActiveGrip() +{ + for (FBPActorGripInformation& Grip : GrippedObjects) + { + if (Grip.IsValid() && !Grip.bIsPaused) + { + return &Grip; + } + } + + for (FBPActorGripInformation& LocalGrip : LocallyGrippedObjects) + { + if (LocalGrip.IsValid() && !LocalGrip.bIsPaused) + { + return &LocalGrip; + } + } + + return nullptr; +} + +void UGripMotionControllerComponent::GetAllGrips(TArray &GripArray) +{ + GripArray.Append(GrippedObjects); + GripArray.Append(LocallyGrippedObjects); +} + +void UGripMotionControllerComponent::GetGrippedObjects(TArray &GrippedObjectsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GrippedObject) + GrippedObjectsArray.Add(GrippedObjects[i].GrippedObject); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GrippedObject) + GrippedObjectsArray.Add(LocallyGrippedObjects[i].GrippedObject); + } + +} + +void UGripMotionControllerComponent::GetGrippedActors(TArray &GrippedObjectsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if(GrippedObjects[i].GetGrippedActor()) + GrippedObjectsArray.Add(GrippedObjects[i].GetGrippedActor()); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GetGrippedActor()) + GrippedObjectsArray.Add(LocallyGrippedObjects[i].GetGrippedActor()); + } + +} + +void UGripMotionControllerComponent::GetGrippedComponents(TArray &GrippedComponentsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GetGrippedComponent()) + GrippedComponentsArray.Add(GrippedObjects[i].GetGrippedComponent()); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GetGrippedComponent()) + GrippedComponentsArray.Add(LocallyGrippedObjects[i].GetGrippedComponent()); + } +} + +// Locally gripped functions +void UGripMotionControllerComponent::Client_NotifyInvalidLocalGrip_Implementation(UObject * LocallyGrippedObject, uint8 GripID, bool bWasAGripConflict) +{ + if (GripID != INVALID_VRGRIP_ID) + { + if (FBPActorGripInformation* GripInfo = GetGripPtrByID(GripID)) + { + DropObjectByInterface(nullptr, GripID); + + if (LocallyGrippedObject && bWasAGripConflict) + { + OnClientAuthGripConflict.Broadcast(LocallyGrippedObject, ClientAuthConflictResolutionMethod); + } + + return; + } + } + + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + + GetGripByObject(FoundGrip, LocallyGrippedObject, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + // Drop it, server told us that it was a bad grip + DropObjectByInterface(nullptr, FoundGrip.GripID); + + if (LocallyGrippedObject && bWasAGripConflict) + { + OnClientAuthGripConflict.Broadcast(LocallyGrippedObject, ClientAuthConflictResolutionMethod); + } +} + +bool UGripMotionControllerComponent::Server_NotifyHandledTransaction_Validate(uint8 GripID) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyHandledTransaction_Implementation(uint8 GripID) +{ + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; i--) + { + if(LocalTransactionBuffer[i].GripID == GripID) + LocalTransactionBuffer.RemoveAt(i); + } +} + +bool UGripMotionControllerComponent::Server_NotifyLocalGripAddedOrChanged_Validate(const FBPActorGripInformation & newGrip) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyLocalGripAddedOrChanged_Implementation(const FBPActorGripInformation & newGrip) +{ + if (!newGrip.GrippedObject || newGrip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive) + { + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID); + return; + } + + if (!LocallyGrippedObjects.Contains(newGrip)) + { + + bool bImplementsInterface = newGrip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + TArray HoldingControllers; + bool bIsHeld = false; + if (bImplementsInterface) + { + IVRGripInterface::Execute_IsHeld(newGrip.GrippedObject, HoldingControllers, bIsHeld); + + if (bIsHeld && ClientAuthConflictResolutionMethod != EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_None) + { + // Its held and doesn't allow more than one grip at a time + if (!IVRGripInterface::Execute_AllowsMultipleGrips(newGrip.GrippedObject)) + { + // Lets see if a different owner is holding it, if so, deny this request + for (FBPGripPair& GripPair : HoldingControllers) + { + if (!GripPair.HoldingController || GripPair.GripID == INVALID_VRGRIP_ID) + { + continue; + } + + // If the controllers have different owners (if its the same then consider them as locally transacting and let it go) + if (GripPair.HoldingController->GetOwner()->GetNetOwner() != this->GetOwner()->GetNetOwner()) + { + switch (ClientAuthConflictResolutionMethod) + { + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_First: + { + // Deny the grip, another connection already came and gripped it + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID, true); + + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + return; + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_Last: + { + // Deny the old grip, another connection came and gripped it + GripPair.HoldingController->DropObjectByInterface(newGrip.GrippedObject, GripPair.GripID); + GripPair.HoldingController->Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, GripPair.GripID, true); + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_DropAll: + { + // Deny both grips + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID, true); + GripPair.HoldingController->DropObjectByInterface(newGrip.GrippedObject, GripPair.GripID); + GripPair.HoldingController->Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, GripPair.GripID, true); + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + return; + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_None: + default: + { + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + }break; + + } + } + } + } + } + } + + + UPrimitiveComponent* PrimComp = nullptr; + AActor* pActor = nullptr; + + PrimComp = newGrip.GetGrippedComponent(); + pActor = newGrip.GetGrippedActor(); + + if (!PrimComp && pActor) + { + PrimComp = Cast(pActor->GetRootComponent()); + } + else if (!pActor && PrimComp) + { + pActor = PrimComp->GetOwner(); + } + + if (!PrimComp || !pActor) + { + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID); + return; + } + + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (bImplementsInterface) + { + //if (IVRGripInterface::Execute_DenyGripping(root)) + // return false; // Interface is saying not to grip it right now + if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers.Num() > 0 && HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + } + + int32 NewIndex = LocallyGrippedObjects.Add(newGrip); + + if (NewIndex != INDEX_NONE && LocallyGrippedObjects.Num() > 0) + { + if (bHadOriginalSettings) + { + LocallyGrippedObjects[NewIndex].bOriginalReplicatesMovement = bOriginalReplication; + LocallyGrippedObjects[NewIndex].bOriginalGravity = bOriginalGravity; + } + else + { + LocallyGrippedObjects[NewIndex].bOriginalReplicatesMovement = pActor->IsReplicatingMovement(); + LocallyGrippedObjects[NewIndex].bOriginalGravity = PrimComp->IsGravityEnabled(); + } + + HandleGripReplication(LocallyGrippedObjects[NewIndex]); + } + + // Initialize the differences, clients will do this themselves on the rep back, this sets up the cache + //HandleGripReplication(LocallyGrippedObjects[LocallyGrippedObjects.Num() - 1]); + } + else + { + int32 IndexFound = INDEX_NONE; + if (LocallyGrippedObjects.Find(newGrip, IndexFound) && IndexFound != INDEX_NONE) + { + FBPActorGripInformation OriginalGrip = LocallyGrippedObjects[IndexFound]; + LocallyGrippedObjects[IndexFound].RepCopy(newGrip); + HandleGripReplication(LocallyGrippedObjects[IndexFound], &OriginalGrip); + } + } + + // Server has to call this themselves + //OnRep_LocallyGrippedObjects(); +} + + +bool UGripMotionControllerComponent::Server_NotifyLocalGripRemoved_Validate(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 OptAngularVelocity, FVector_NetQuantize100 OptLinearVelocity) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyLocalGripRemoved_Implementation(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 OptAngularVelocity, FVector_NetQuantize100 OptLinearVelocity) +{ + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + GetGripByID(FoundGrip, GripID, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + if (FoundGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + switch (FoundGrip.GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* DroppingActor = FoundGrip.GetGrippedActor()) + { + if (IsValid(DroppingActor) && TransformAtDrop.IsValid()) + { + DroppingActor->SetActorTransform(TransformAtDrop, false, nullptr, ETeleportType::None); + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* DroppingComp = FoundGrip.GetGrippedComponent()) + { + if (IsValid(DroppingComp) && TransformAtDrop.IsValid()) + { + DroppingComp->SetWorldTransform(TransformAtDrop, false, nullptr, ETeleportType::None); + } + } + }break; + default:break; + } + } + + if (!DropObjectByInterface_Implementation(nullptr, FoundGrip.GripID, OptAngularVelocity, OptLinearVelocity, true)) + { + DropGrip_Implementation(FoundGrip, false, OptAngularVelocity, OptLinearVelocity,true); + } +} + + +bool UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Validate( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Implementation( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo) +{ + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GripID); + if (GripInfo != nullptr) + { + FBPActorGripInformation OriginalGrip = *GripInfo; + + // I override the = operator now so that it won't set the lerp components + GripInfo->SecondaryGripInfo.RepCopy(SecondaryGripInfo); + + // Initialize the differences, clients will do this themselves on the rep back + HandleGripReplication(*GripInfo, &OriginalGrip); + } + +} + +bool UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Retain_Validate( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Retain_Implementation( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform) +{ + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GripID); + if (GripInfo != nullptr) + { + FBPActorGripInformation OriginalGrip = *GripInfo; + + // I override the = operator now so that it won't set the lerp components + GripInfo->SecondaryGripInfo.RepCopy(SecondaryGripInfo); + GripInfo->RelativeTransform = NewRelativeTransform; + + // Initialize the differences, clients will do this themselves on the rep back + HandleGripReplication(*GripInfo, &OriginalGrip); + } + +} +void UGripMotionControllerComponent::GetControllerDeviceID(FXRDeviceId & DeviceID, EBPVRResultSwitch &Result, bool bCheckOpenVROnly) +{ + EControllerHand ControllerHandIndex; + if (!IMotionController::GetHandEnumForSourceName(MotionSource, ControllerHandIndex)) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + TArray XRAssetSystems = IModularFeatures::Get().GetModularFeatureImplementations(IXRSystemAssets::GetModularFeatureName()); + for (IXRSystemAssets* AssetSys : XRAssetSystems) + { + if (bCheckOpenVROnly && !AssetSys->GetSystemName().IsEqual(FName(TEXT("SteamVR")))) + continue; + + const int32 XRID = AssetSys->GetDeviceId(ControllerHandIndex); + + if (XRID != INDEX_NONE) + { + DeviceID = FXRDeviceId(AssetSys, XRID); + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + DeviceID = FXRDeviceId(); + Result = EBPVRResultSwitch::OnFailed; + return; +} + + +/* +* +* Custom late update manager implementation +* +*/ + +FExpandedLateUpdateManager::FExpandedLateUpdateManager() + : LateUpdateGameWriteIndex(0) + , LateUpdateRenderReadIndex(0) +{ +} + +void FExpandedLateUpdateManager::Setup(const FTransform& ParentToWorld, UGripMotionControllerComponent* Component, bool bSkipLateUpdate) +{ + if (!Component) + return; + + check(IsInGameThread()); + + + UpdateStates[LateUpdateGameWriteIndex].Primitives.Reset(); + UpdateStates[LateUpdateGameWriteIndex].ParentToWorld = ParentToWorld; + + //Add additional late updates registered to this controller that aren't children and aren't gripped + //This array is editable in blueprint and can be used for things like arms or the like. + for (UPrimitiveComponent* primComp : Component->AdditionalLateUpdateComponents) + { + if (primComp) + GatherLateUpdatePrimitives(primComp); + } + + ProcessGripArrayLateUpdatePrimitives(Component, Component->LocallyGrippedObjects); + ProcessGripArrayLateUpdatePrimitives(Component, Component->GrippedObjects); + + GatherLateUpdatePrimitives(Component); + //GatherLateUpdatePrimitives(Component); + + UpdateStates[LateUpdateGameWriteIndex].bSkip = bSkipLateUpdate; + ++UpdateStates[LateUpdateGameWriteIndex].TrackingNumber; + + int32 NextFrameRenderReadIndex = LateUpdateGameWriteIndex; + LateUpdateGameWriteIndex = 1 - LateUpdateGameWriteIndex; + + ENQUEUE_RENDER_COMMAND(UpdateLateUpdateRenderReadIndexCommand)( + [NextFrameRenderReadIndex, this](FRHICommandListImmediate& RHICmdList) + { + LateUpdateRenderReadIndex = NextFrameRenderReadIndex; + }); + +} + +//void FExpandedLateUpdateManager::Apply_RenderThread(FSceneInterface* Scene, const int32 FrameNumber, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform) +void FExpandedLateUpdateManager::Apply_RenderThread(FSceneInterface* Scene, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform) +{ + check(IsInRenderingThread()); + + + if (!UpdateStates[LateUpdateRenderReadIndex].Primitives.Num() || UpdateStates[LateUpdateRenderReadIndex].bSkip) + { + return; + } + + const FTransform OldCameraTransform = OldRelativeTransform * UpdateStates[LateUpdateRenderReadIndex].ParentToWorld; + const FTransform NewCameraTransform = NewRelativeTransform * UpdateStates[LateUpdateRenderReadIndex].ParentToWorld; + const FMatrix LateUpdateTransform = (OldCameraTransform.Inverse() * NewCameraTransform).ToMatrixWithScale(); + + bool bIndicesHaveChanged = false; + + /* + // Under HMD late-latching senario, Apply_RenderThread will be called twice in same frame under PreRenderViewFamily_RenderThread and + // LateLatchingViewFamily_RenderThread. We don't want to apply PrimitivePair.Value = -1 directly on UpdateStates[LateUpdateRenderReadIndex].Primitives + // because the modification will affect second Apply_RenderThread's logic under LateLatchingViewFamily_RenderThread. + // Since the list is very small(only affect stuff attaching to the controller), we just make a local copy PrimitivesLocal. + TMap PrimitivesLocal = UpdateStates[LateUpdateRenderReadIndex].Primitives; + for (auto& PrimitivePair : PrimitivesLocal)*/ + for (auto& PrimitivePair : UpdateStates[LateUpdateRenderReadIndex].Primitives) + { + if (PrimitivePair.Value == -1) + continue; + + FPrimitiveSceneInfo* RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(PrimitivePair.Value); + FPrimitiveSceneInfo* CachedSceneInfo = PrimitivePair.Key; + + // If the retrieved scene info is different than our cached scene info then the scene has changed in the meantime + // and we need to search through the entire scene to make sure it still exists. + if (CachedSceneInfo != RetrievedSceneInfo) + { + bIndicesHaveChanged = true; + break; // No need to continue here, as we are going to brute force the scene primitives below anyway. + } + else if (CachedSceneInfo->Proxy) + { + CachedSceneInfo->Proxy->ApplyLateUpdateTransform(LateUpdateTransform); + PrimitivePair.Value = -1; // Set the cached index to -1 to indicate that this primitive was already processed + /*if (FrameNumber >= 0) + { + CachedSceneInfo->Proxy->SetPatchingFrameNumber(FrameNumber); + }*/ + } + } + + // Indices have changed, so we need to scan the entire scene for primitives that might still exist + if (bIndicesHaveChanged) + { + int32 Index = 0; + FPrimitiveSceneInfo* RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(Index++); + while (RetrievedSceneInfo) + { + /*int32* PrimitiveIndex = PrimitivesLocal.Find(RetrievedSceneInfo); + if (RetrievedSceneInfo->Proxy && PrimitiveIndex != nullptr && *PrimitiveIndex >= 0)*/ + if (RetrievedSceneInfo->Proxy && UpdateStates[LateUpdateRenderReadIndex].Primitives.Contains(RetrievedSceneInfo) && UpdateStates[LateUpdateRenderReadIndex].Primitives[RetrievedSceneInfo] >= 0) + { + RetrievedSceneInfo->Proxy->ApplyLateUpdateTransform(LateUpdateTransform); + /*if (FrameNumber >= 0) + { + RetrievedSceneInfo->Proxy->SetPatchingFrameNumber(FrameNumber); + }*/ + } + RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(Index++); + } + } +} + +void FExpandedLateUpdateManager::CacheSceneInfo(USceneComponent* Component) +{ + ensureMsgf(!Component->IsUsingAbsoluteLocation() && !Component->IsUsingAbsoluteRotation(), TEXT("SceneComponents that use absolute location or rotation are not supported by the LateUpdateManager")); + // If a scene proxy is present, cache it + UPrimitiveComponent* PrimitiveComponent = dynamic_cast(Component); + if (PrimitiveComponent && PrimitiveComponent->SceneProxy) + { + FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveComponent->SceneProxy->GetPrimitiveSceneInfo(); + if (PrimitiveSceneInfo && PrimitiveSceneInfo->IsIndexValid()) + { + UpdateStates[LateUpdateGameWriteIndex].Primitives.Emplace(PrimitiveSceneInfo, PrimitiveSceneInfo->GetIndex()); + } + } +} + +void FExpandedLateUpdateManager::GatherLateUpdatePrimitives(USceneComponent* ParentComponent) +{ + CacheSceneInfo(ParentComponent); + TArray DirectComponents; + + // Std late updates + ParentComponent->GetChildrenComponents(true, DirectComponents); + for (USceneComponent* Component : DirectComponents) + { + if (Component != nullptr) + { + CacheSceneInfo(Component); + } + } +} + +void FExpandedLateUpdateManager::ProcessGripArrayLateUpdatePrimitives(UGripMotionControllerComponent * MotionControllerComponent, TArray & GripArray) +{ + for (FBPActorGripInformation actor : GripArray) + { + // Skip actors that are colliding if turning off late updates during collision. + // Also skip turning off late updates for SweepWithPhysics, as it should always be locked to the hand + if (!actor.GrippedObject || actor.GripCollisionType == EGripCollisionType::EventsOnly) + continue; + + // Handle late updates even with attachment, we need to add it to a skip list for the primary gatherer to process + if (actor.GripCollisionType == EGripCollisionType::AttachmentGrip) + { + continue; + } + + // Don't allow late updates with server sided movement, there is no point + if (actor.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !MotionControllerComponent->IsServer()) + continue; + + // Don't late update paused grips + if (actor.bIsPaused) + continue; + + switch (actor.GripLateUpdateSetting) + { + case EGripLateUpdateSettings::LateUpdatesAlwaysOff: + { + continue; + }break; + case EGripLateUpdateSettings::NotWhenColliding: + { + if (actor.bColliding && actor.GripCollisionType != EGripCollisionType::SweepWithPhysics && + actor.GripCollisionType != EGripCollisionType::PhysicsOnly) + continue; + }break; + case EGripLateUpdateSettings::NotWhenDoubleGripping: + { + if (actor.SecondaryGripInfo.bHasSecondaryAttachment) + continue; + }break; + case EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping: + { + if ( + (actor.bColliding && actor.GripCollisionType != EGripCollisionType::SweepWithPhysics && actor.GripCollisionType != EGripCollisionType::PhysicsOnly) || + (actor.SecondaryGripInfo.bHasSecondaryAttachment) + ) + { + continue; + } + }break; + case EGripLateUpdateSettings::LateUpdatesAlwaysOn: + default: + {}break; + } + + // Don't run late updates if we have a grip script that denies it + if (actor.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(actor.GrippedObject, GripScripts)) + { + bool bContinueOn = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyLateUpdates()) + { + bContinueOn = true; + break; + } + } + + if (bContinueOn) + continue; + } + } + + // Get late update primitives + switch (actor.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + AActor * pActor = actor.GetGrippedActor(); + if (pActor) + { + if (USceneComponent * rootComponent = pActor->GetRootComponent()) + { + GatherLateUpdatePrimitives(rootComponent); + } + } + + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + UPrimitiveComponent * cPrimComp = actor.GetGrippedComponent(); + if (cPrimComp) + { + GatherLateUpdatePrimitives(cPrimComp); + } + }break; + } + } +} + +void UGripMotionControllerComponent::GetHandType(EControllerHand& Hand) +{ + if (!IMotionController::GetHandEnumForSourceName(MotionSource, Hand)) + { + // Check if the palm motion source extension is being used + // I assume eventually epic will handle this case + if (MotionSource.Compare(FName(TEXT("RightPalm"))) == 0) + { + Hand = EControllerHand::Right; + } + // Could skip this and default to left now but would rather check + else if (MotionSource.Compare(FName(TEXT("LeftPalm"))) == 0) + { + Hand = EControllerHand::Left; + } + else + { + Hand = EControllerHand::Left; + } + } +} + +void UGripMotionControllerComponent::SetCustomPivotComponent(USceneComponent * NewCustomPivotComponent, FName PivotSocketName) +{ + CustomPivotComponent = NewCustomPivotComponent; + CustomPivotComponentSocketName = PivotSocketName; +} + +FTransform UGripMotionControllerComponent::GetPivotTransform_BP() +{ + return GetPivotTransform(); +} + +FVector UGripMotionControllerComponent::GetPivotLocation_BP() +{ + return GetPivotLocation(); +} + +FTransform UGripMotionControllerComponent::ConvertToControllerRelativeTransform(const FTransform & InTransform) +{ + return InTransform.GetRelativeTransform(!bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketTransform(CustomPivotComponentSocketName) : this->GetComponentTransform()); +} + +FTransform UGripMotionControllerComponent::ConvertToGripRelativeTransform(const FTransform& GrippedActorTransform, const FTransform & InTransform) +{ + return InTransform.GetRelativeTransform(GrippedActorTransform); +} + +bool UGripMotionControllerComponent::GetIsObjectHeld(const UObject * ObjectToCheck) +{ + if (!ObjectToCheck) + return false; + + return (GrippedObjects.FindByKey(ObjectToCheck) || LocallyGrippedObjects.FindByKey(ObjectToCheck)); +} + +bool UGripMotionControllerComponent::GetIsHeld(const AActor * ActorToCheck) +{ + if (!ActorToCheck) + return false; + + return (GrippedObjects.FindByKey(ActorToCheck) || LocallyGrippedObjects.FindByKey(ActorToCheck)); +} + +bool UGripMotionControllerComponent::GetIsComponentHeld(const UPrimitiveComponent * ComponentToCheck) +{ + if (!ComponentToCheck) + return false; + + return (GrippedObjects.FindByKey(ComponentToCheck) || LocallyGrippedObjects.FindByKey(ComponentToCheck)); + + return false; +} + +bool UGripMotionControllerComponent::GetIsSecondaryAttachment(const USceneComponent * ComponentToCheck, FBPActorGripInformation & Grip) +{ + if (!ComponentToCheck) + return false; + + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].SecondaryGripInfo.bHasSecondaryAttachment && GrippedObjects[i].SecondaryGripInfo.SecondaryAttachment == ComponentToCheck) + { + Grip = GrippedObjects[i]; + return true; + } + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].SecondaryGripInfo.bHasSecondaryAttachment && LocallyGrippedObjects[i].SecondaryGripInfo.SecondaryAttachment == ComponentToCheck) + { + Grip = LocallyGrippedObjects[i]; + return true; + } + } + + return false; +} + +bool UGripMotionControllerComponent::HasGrippedObjects() +{ + return GrippedObjects.Num() > 0 || LocallyGrippedObjects.Num() > 0; +} + +bool UGripMotionControllerComponent::SetUpPhysicsHandle_BP(const FBPActorGripInformation &Grip) +{ + return SetUpPhysicsHandle(Grip); +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle_BP(const FBPActorGripInformation &Grip) +{ + return DestroyPhysicsHandle(Grip); +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle_BP(const FBPActorGripInformation& Grip, bool bFullyRecreate) +{ + return UpdatePhysicsHandle(Grip.GripID, bFullyRecreate); +} + +bool UGripMotionControllerComponent::GetPhysicsHandleSettings(const FBPActorGripInformation& Grip, FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsOut) +{ + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + return false; + + PhysicsHandleSettingsOut.FillFrom(HandleInfo); + return true; +} + +bool UGripMotionControllerComponent::SetPhysicsHandleSettings(const FBPActorGripInformation& Grip, const FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsIn) +{ + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + return false; + + PhysicsHandleSettingsIn.FillTo(HandleInfo); + return UpdatePhysicsHandle(Grip, true); +} + + +void UGripMotionControllerComponent::UpdatePhysicsHandleTransform_BP(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform) +{ + return UpdatePhysicsHandleTransform(GrippedActor, NewTransform); +} + +bool UGripMotionControllerComponent::GetGripDistance_BP(FBPActorGripInformation &Grip, FVector ExpectedLocation, float & CurrentDistance) +{ + if (!Grip.GrippedObject) + return false; + + UPrimitiveComponent * RootComp = nullptr; + + if (Grip.GripTargetType == EGripTargetType::ActorGrip) + { + RootComp = Cast(Grip.GetGrippedActor()->GetRootComponent()); + } + else + RootComp = Grip.GetGrippedComponent(); + + if (!RootComp) + return false; + + FVector CheckDistance; + if (!GetPhysicsJointLength(Grip, RootComp, CheckDistance)) + { + CheckDistance = (ExpectedLocation - RootComp->GetComponentLocation()); + } + // Set grip distance now for people to use + CurrentDistance = CheckDistance.Size(); + return true; +} + +bool UGripMotionControllerComponent::GripControllerIsTracked() const +{ + return IsTracked(); +} + +bool UGripMotionControllerComponent::HasAuthority() const +{ + return bHasAuthority; +} + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp new file mode 100644 index 0000000..96f36c8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp @@ -0,0 +1,233 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Default.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_Default) + +#include "VRGripInterface.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GameFramework/WorldSettings.h" +#include "GripMotionControllerComponent.h" + +UGS_Default::UGS_Default(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; +} + +void UGS_Default::GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) +{ + if (Grip.SecondaryGripInfo.GripLerpState != EGripLerpState::EndLerp) + { + //float Scaler = 1.0f; + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + /*Grip.SecondaryScaler*/ Scaler = FVector(frontLoc.Size() / frontLocOrig.Size()); + //bRescalePhysicsGrips = true; // This is for the physics grips + } + } +} + +void UGS_Default::ApplySmoothingAndLerp(FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, float DeltaTime) +{ + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) // Lerp into the new grip to smooth the transition + { + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + FVector SmoothedValue = Grip.AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + + frontLoc = FMath::Lerp(SmoothedValue, frontLoc, Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED); + }*/ + + frontLocOrig = FMath::Lerp(frontLocOrig, frontLoc, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + /*else if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::ConstantLerp_DEPRECATED) // If there is a frame by frame lerp + { + FVector SmoothedValue = Grip.AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + + frontLoc = FMath::Lerp(SmoothedValue, frontLoc, Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED); + }*/ +} + +bool UGS_Default::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform& WorldTransform, + const FTransform& ParentTransform, + FBPActorGripInformation& Grip, + AActor* actor, + UPrimitiveComponent* root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + // Just simple transform setting + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + /*if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && + Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::ConstantLerp_DEPRECATED; + } + else*/ + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); // Get our pivot point + const FTransform PivotToWorld = FTransform(FQuat::Identity, BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + frontLoc = Grip.SecondaryGripInfo.LastRelativeLocation; + + frontLocOrig = FMath::Lerp(frontLoc, frontLocOrig, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + } + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripDistanceInfluence_DEPRECATED) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDeadZone_DEPRECATED) / FMath::Max(Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDistanceToZero_DEPRECATED, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + }*/ + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + } + return true; +} + +void UGS_Default::CalculateSecondaryLocation(FVector& frontLoc, const FVector& BasePoint, FBPActorGripInformation& Grip, UGripMotionControllerComponent* GrippingController) +{ + bool bPulledControllerLoc = false; + if (UGripMotionControllerComponent* OtherController = Cast(Grip.SecondaryGripInfo.SecondaryAttachment)) + { + bool bPulledCurrentTransform = false; + + if (IsValid(OtherController->CustomPivotComponent)) + { + FTransform SecondaryTrans = FTransform::Identity; + SecondaryTrans = OtherController->GetPivotTransform(); + bPulledControllerLoc = true; + frontLoc = SecondaryTrans.GetLocation() - BasePoint; + } + } + + if (!bPulledControllerLoc) + { + frontLoc = Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation() - BasePoint; + } +} + +void UGS_ExtendedDefault::GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) +{ + if (Grip.SecondaryGripInfo.GripLerpState != EGripLerpState::EndLerp) + { + + //float Scaler = 1.0f; + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + /*Grip.SecondaryScaler*/ Scaler = FVector(frontLoc.Size() / frontLocOrig.Size()); + //bRescalePhysicsGrips = true; // This is for the physics grips + + if (bLimitGripScaling) + { + // Get the total scale after modification + // #TODO: convert back to singular float version? Can get Min() & Max() to convert the float to a range...think about it + FVector WorldScale = /*WorldTransform*/SecondaryTransform.GetScale3D(); + FVector CombinedScale = WorldScale * Scaler; + + // Clamp to the minimum and maximum values + CombinedScale.X = FMath::Clamp(CombinedScale.X, MinimumGripScaling.X, MaximumGripScaling.X); + CombinedScale.Y = FMath::Clamp(CombinedScale.Y, MinimumGripScaling.Y, MaximumGripScaling.Y); + CombinedScale.Z = FMath::Clamp(CombinedScale.Z, MinimumGripScaling.Z, MaximumGripScaling.Z); + + // Recreate in scaler form so that the transform chain below works as normal + Scaler = CombinedScale / WorldScale; + } + //Scaler = Grip.SecondaryScaler; + } + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp new file mode 100644 index 0000000..884fd33 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp @@ -0,0 +1,601 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_GunTools.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_GunTools) + +#include "VRGripInterface.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "IXRTrackingSystem.h" +#include "VRGlobalSettings.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "VRRootComponent.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +//#include "Camera/CameraComponent.h" +#include "ReplicatedVRCameraComponent.h" +#include "DrawDebugHelpers.h" + +UGS_GunTools::UGS_GunTools(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; + PivotOffset = FVector::ZeroVector; + VirtualStockComponent = nullptr; + MountWorldTransform = FTransform::Identity; + //StockSnapOffset = FVector(0.f, 0.f, 0.f); + bIsMounted = false; + + + bHasRecoil = false; + bApplyRecoilAsPhysicalForce = false; + MaxRecoilTranslation = FVector::ZeroVector; + MaxRecoilRotation = FVector::ZeroVector; + MaxRecoilScale = FVector(1.f); + bHasActiveRecoil = false; + DecayRate = 20.f; + LerpRate = 30.f; + + BackEndRecoilStorage = FTransform::Identity; + + bUseGlobalVirtualStockSettings = true; + + bUseHighQualityRemoteSimulation = false; + + bInjectPrePhysicsHandle = true; + //bInjectPostPhysicsHandle = true; + WeaponRootOrientationComponent = NAME_None; + OrientationComponentRelativeFacing = FTransform::Identity; + StoredRootOffset = FQuat::Identity; +} + +void UGS_GunTools::OnBeginPlay_Implementation(UObject* CallingOwner) +{ + // Grip base has no super of this + + if (WeaponRootOrientationComponent.IsValid()) + { + if (AActor * Owner = GetOwner()) + { + FName CurrentCompName = NAME_None; + for (UActorComponent* ChildComp : Owner->GetComponents()) + { + CurrentCompName = ChildComp->GetFName(); + if (CurrentCompName == NAME_None) + continue; + + if (CurrentCompName == WeaponRootOrientationComponent) + { + if (USceneComponent * SceneComp = Cast(ChildComp)) + { + OrientationComponentRelativeFacing = SceneComp->GetRelativeTransform(); + } + + break; + } + } + } + } +} + +void UGS_GunTools::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + StoredRootOffset = HandleInfo->RootBoneRotation.GetRotation().Inverse() * OrientationComponentRelativeFacing.GetRotation(); + + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + KinPose.SetRotation(KinPose.GetRotation() * StoredRootOffset); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * StoredRootOffset); + } + else + { + StoredRootOffset = FQuat::Identity; + } + + if (GripInfo.bIsSlotGrip && !PivotOffset.IsZero()) + { + KinPose.SetLocation(KinPose.TransformPosition(PivotOffset)); + HandleInfo->COMPosition.SetLocation(HandleInfo->COMPosition.TransformPosition(PivotOffset)); + } +} + +/*void UGS_GunTools::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) +{ +}*/ + +bool UGS_GunTools::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + bool bSkipHighQualityOperations = !bUseHighQualityRemoteSimulation && !GrippingController->HasAuthority(); + + /*if (!GunViewExtension.IsValid() && GEngine) + { + GunViewExtension = FSceneViewExtensions::NewExtension(GrippingController); + }*/ + + // Just simple transform setting + if (bHasRecoil && bHasActiveRecoil) + { + BackEndRecoilStorage.Blend(BackEndRecoilStorage, BackEndRecoilTarget, FMath::Clamp(LerpRate * DeltaTime, 0.f, 1.f)); + BackEndRecoilTarget.Blend(BackEndRecoilTarget, FTransform::Identity, FMath::Clamp(DecayRate * DeltaTime, 0.f, 1.f)); + bHasActiveRecoil = !BackEndRecoilTarget.Equals(FTransform::Identity); + + if (!bHasActiveRecoil) + { + BackEndRecoilStorage.SetIdentity(); + BackEndRecoilTarget.SetIdentity(); + } + } + + if (bHasActiveRecoil) + { + // Using a matrix to avoid FTransform inverse math issues + FTransform relTransform(Grip.RelativeTransform.ToInverseMatrixWithScale()); + + // Eventually may want to adjust the pivot of the recoil rotation by the PivotOffset vector... + FVector Pivot = relTransform.GetLocation() + PivotOffset; + const FTransform PivotToWorld = FTransform(FQuat::Identity, Pivot); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -Pivot); + + WorldTransform = WorldToPivot * BackEndRecoilStorage * PivotToWorld * Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + } + else + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) + { + if (!bSkipHighQualityOperations && bUseVirtualStock) + { + if (IsValid(VirtualStockComponent)) + { + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VirtualStockComponent->GetComponentRotation()); + MountWorldTransform = FTransform(PureYaw.Quaternion(), VirtualStockComponent->GetComponentLocation() + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)); + } + /*else if (GrippingController->bHasAuthority && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot = FQuat::Identity; + FVector curLoc = FVector::ZeroVector; + + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curLoc)) + { + // Translate hmd offset by the gripping controllers parent component, this should be in the same space + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curRot.Rotator()); + + if (AVRCharacter* OwningCharacter = Cast(GrippingController->GetOwner())) + { + if (!OwningCharacter->bRetainRoomscale) + { + curLoc.X = 0.0f; + curLoc.Y = 0.0f; + curLoc += PureYaw.RotateVector(FVector(-OwningCharacter->VRRootReference->VRCapsuleOffset.X, -OwningCharacter->VRRootReference->VRCapsuleOffset.Y, -OwningCharacter->VRRootReference->GetScaledCapsuleHalfHeight())); + } + } + + MountWorldTransform = FTransform(PureYaw.Quaternion(), curLoc + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)) * GrippingController->GetAttachParent()->GetComponentTransform(); + } + }*/ + else if(IsValid(CameraComponent)) + { + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(CameraComponent->GetComponentRotation()); + MountWorldTransform = FTransform(PureYaw.Quaternion(), CameraComponent->GetComponentLocation() + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)); + } + + float StockSnapDistance = FMath::Square(VirtualStockSettings.StockSnapDistance); + float DistSquared = FVector::DistSquared(ParentTransform.GetTranslation(), MountWorldTransform.GetTranslation()); + + if (!VirtualStockSettings.bUseDistanceBasedStockSnapping || (DistSquared <= StockSnapDistance)) + { + + float StockSnapLerpThresh = FMath::Square(VirtualStockSettings.StockSnapLerpThreshold); + + if (StockSnapLerpThresh > 0.0f) + VirtualStockSettings.StockLerpValue = 1.0f - FMath::Clamp((DistSquared - (StockSnapDistance - StockSnapLerpThresh)) / StockSnapLerpThresh, 0.0f, 1.0f); + else + VirtualStockSettings.StockLerpValue = 1.0f; // Just skip lerping logic + + if (!bIsMounted) + { + VirtualStockSettings.StockHandSmoothing.ResetSmoothingFilter(); + + // Mount up + bIsMounted = true; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + + // Adjust the mount location to follow the Z of the primary hand + if (VirtualStockSettings.bAdjustZOfStockToPrimaryHand) + { + FVector WorldTransVec = MountWorldTransform.GetTranslation(); + + if (WorldTransVec.Z >= ParentTransform.GetTranslation().Z) + { + WorldTransVec.Z = ParentTransform.GetTranslation().Z; + MountWorldTransform.SetLocation(WorldTransVec); + } + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (VirtualStockSettings.bDebugDrawVirtualStock) + { + DrawDebugLine(GetWorld(), ParentTransform.GetTranslation(), MountWorldTransform.GetTranslation(), FColor::Red); + DrawDebugLine(GetWorld(), Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation(), MountWorldTransform.GetTranslation(), FColor::Green); + DrawDebugSphere(GetWorld(), MountWorldTransform.GetTranslation(), 10.f, 32, FColor::White); + } +#endif + } + else + { + if (bIsMounted) + { + bIsMounted = false; + VirtualStockSettings.StockLerpValue = 0.0f; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + } + } + } + else + { + if (bIsMounted) + { + bIsMounted = false; + VirtualStockSettings.StockLerpValue = 0.0f; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + } + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform NewWorldTransform = WorldTransform; + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); + FVector Pivot = ParentTransform.GetLocation(); + + if (Grip.bIsSlotGrip) + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GrippingController->GetPhysicsGrip(Grip)) + { + Pivot = SecondaryTransform.TransformPositionNoScale(SecondaryTransform.InverseTransformPositionNoScale(Pivot) + (StoredRootOffset * PhysHandle->RootBoneRotation.GetRotation()).RotateVector(PivotOffset)); + } + else + { + Pivot = SecondaryTransform.TransformPositionNoScale(SecondaryTransform.InverseTransformPositionNoScale(Pivot) + OrientationComponentRelativeFacing.GetRotation().RotateVector(PivotOffset)); + } + } + + // Debug draw for COM movement with physics grips +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + static const auto CVarDrawCOMDebugSpheresAccess = IConsoleManager::Get().FindConsoleVariable(TEXT("vr.DrawDebugCenterOfMassForGrips")); + if (CVarDrawCOMDebugSpheresAccess->GetInt() > 0) + { + DrawDebugSphere(GetWorld(), Pivot, 5, 32, FColor::Orange, false); + } +#endif + + const FTransform PivotToWorld = FTransform(FQuat::Identity, Pivot);//BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -Pivot);//-BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + WorldTransform.Blend(WorldTransform, RelativeTransOnSecondaryRelease* GrippingController->GetPivotTransform(), FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + return true; + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + GunTools_ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime, bSkipHighQualityOperations); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + if (!bSkipHighQualityOperations && AdvSecondarySettings.bUseAdvancedSecondarySettings && AdvSecondarySettings.bUseSecondaryGripDistanceInfluence) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - AdvSecondarySettings.GripInfluenceDeadZone) / FMath::Max(AdvSecondarySettings.GripInfluenceDistanceToZero, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + } + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get shoulder mount addition rotation + if (!bSkipHighQualityOperations && bUseVirtualStock && bIsMounted) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation(), (frontLoc + BasePoint) - MountWorldTransform.GetTranslation()); + FQuat MountAdditionRotation = FQuat::FindBetweenVectors(frontLocOrig, GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation()); + + if (VirtualStockSettings.StockLerpValue < 1.0f) + { + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + FTransform NA = FTransform(rotVal * MountAdditionRotation, FVector::ZeroVector, Scaler); + FTransform NB = FTransform(FQuat::FindBetweenVectors(frontLocOrig, frontLoc), FVector::ZeroVector, Scaler); + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + NA.Blend(NB, NA, VirtualStockSettings.StockLerpValue); + + NewWorldTransform = WorldTransform * WorldToPivot * NA * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * MountAdditionRotation * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + + // Get shoulder mount addition rotation + if (!bSkipHighQualityOperations && bUseVirtualStock && bIsMounted) + { + FQuat MountAdditionRotation = FQuat::FindBetweenVectors(frontLocOrig, GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation()); + + // If it is exactly 1.0f then lets skip all of the extra logic and just set it + if (VirtualStockSettings.StockLerpValue < 1.0f) + { + FTransform NA = FTransform(MountAdditionRotation, FVector::ZeroVector, Scaler); + FTransform NB = FTransform(FQuat::Identity, FVector::ZeroVector, Scaler); + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + NA.Blend(NB, NA, VirtualStockSettings.StockLerpValue); + NewWorldTransform = WorldTransform * WorldToPivot * NA * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * MountAdditionRotation * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + } + + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) + { + WorldTransform.Blend(NewWorldTransform, WorldTransform, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else + { + WorldTransform = NewWorldTransform; + } + + if (bIsMounted && VirtualStockSettings.bSmoothStockHand) + { + if (GrippingController->GetAttachParent()) + { + FTransform ParentTrans = GrippingController->GetAttachParent()->GetComponentTransform(); + FTransform ParentRel = WorldTransform * ParentTrans.Inverse(); + ParentRel.Blend(ParentRel, VirtualStockSettings.StockHandSmoothing.RunFilterSmoothing(ParentRel, DeltaTime), VirtualStockSettings.SmoothingValueForStock); + WorldTransform = ParentRel * ParentTrans; + } + } + + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + RelativeTransOnSecondaryRelease = WorldTransform.GetRelativeTransform(GrippingController->GetPivotTransform()); + } + } + + return true; +} + +void UGS_GunTools::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + + if (bUseGlobalVirtualStockSettings) + { + if (GrippingController->IsLocallyControlled()) + { + FBPVirtualStockSettings VirtualSettings; + UVRGlobalSettings::GetVirtualStockGlobalSettings(VirtualSettings); + VirtualStockSettings.CopyFrom(VirtualSettings); + } + } + + // Super doesn't do anything on grip + + // Reset smoothing filters + if (AdvSecondarySettings.bUseConstantGripScaler) + { + if (AdvSecondarySettings.bUseGlobalSmoothingSettings) + { + const UVRGlobalSettings& VRSettings = *GetDefault(); + AdvSecondarySettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + AdvSecondarySettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + AdvSecondarySettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + } + + AdvSecondarySettings.SecondarySmoothing.ResetSmoothingFilter(); + } + + if (bUseVirtualStock) + { + ResetStockVariables(); + } + + GetVirtualStockTarget(GrippingController); +} + +void UGS_GunTools::GetVirtualStockTarget(UGripMotionControllerComponent * GrippingController) +{ + if (GrippingController && (GrippingController->HasAuthority() || bUseHighQualityRemoteSimulation)) + { + if (AVRBaseCharacter * vrOwner = Cast(GrippingController->GetOwner())) + { + CameraComponent = vrOwner->VRReplicatedCamera; + return; + } + else + { + TArray children = GrippingController->GetOwner()->GetRootComponent()->GetAttachChildren(); + + for (int i = 0; i < children.Num(); i++) + { + if (children[i]->IsA(UCameraComponent::StaticClass())) + { + CameraComponent = children[i]; + return; + } + } + } + + CameraComponent = nullptr; + } +} + +void UGS_GunTools::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) +{ + // Super doesn't do anything on Secondary grip + + // Reset smoothing filters + if (AdvSecondarySettings.bUseConstantGripScaler) + { + if (AdvSecondarySettings.bUseGlobalSmoothingSettings) + { + const UVRGlobalSettings& VRSettings = *GetDefault(); + AdvSecondarySettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + AdvSecondarySettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + AdvSecondarySettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + } + + AdvSecondarySettings.SecondarySmoothing.ResetSmoothingFilter(); + } + + if (bUseVirtualStock) + ResetStockVariables(); +} + +void UGS_GunTools::ResetRecoil() +{ + BackEndRecoilStorage = FTransform::Identity; + BackEndRecoilTarget = FTransform::Identity; +} + +void UGS_GunTools::AddRecoilInstance(const FTransform & RecoilAddition, FVector Optional_Location) +{ + if (!bHasRecoil) + return; + + if (bApplyRecoilAsPhysicalForce) + { + if (FBodyInstance * BodyInst = GetParentBodyInstance()) + { + BodyInst->AddImpulseAtPosition(RecoilAddition.GetLocation(), Optional_Location); + } + } + else + { + BackEndRecoilTarget += RecoilAddition; + + FVector CurVec = BackEndRecoilTarget.GetTranslation(); + CurVec.X = FMath::Clamp(CurVec.X, -FMath::Abs(MaxRecoilTranslation.X), FMath::Abs(MaxRecoilTranslation.X)); + CurVec.Y = FMath::Clamp(CurVec.Y, -FMath::Abs(MaxRecoilTranslation.Y), FMath::Abs(MaxRecoilTranslation.Y)); + CurVec.Z = FMath::Clamp(CurVec.Z, -FMath::Abs(MaxRecoilTranslation.Z), FMath::Abs(MaxRecoilTranslation.Z)); + BackEndRecoilTarget.SetTranslation(CurVec); + + FVector CurScale = BackEndRecoilTarget.GetScale3D(); + CurScale.X = FMath::Clamp(CurScale.X, -FMath::Abs(MaxRecoilScale.X), FMath::Abs(MaxRecoilScale.X)); + CurScale.Y = FMath::Clamp(CurScale.Y, -FMath::Abs(MaxRecoilScale.Y), FMath::Abs(MaxRecoilScale.Y)); + CurScale.Z = FMath::Clamp(CurScale.Z, -FMath::Abs(MaxRecoilScale.Z), FMath::Abs(MaxRecoilScale.Z)); + BackEndRecoilTarget.SetScale3D(CurScale); + + FRotator curRot = BackEndRecoilTarget.Rotator(); + curRot.Pitch = FMath::Clamp(curRot.Pitch, -FMath::Abs(MaxRecoilRotation.Y), FMath::Abs(MaxRecoilRotation.Y)); + curRot.Yaw = FMath::Clamp(curRot.Yaw, -FMath::Abs(MaxRecoilRotation.Z), FMath::Abs(MaxRecoilRotation.Z)); + curRot.Roll = FMath::Clamp(curRot.Roll, -FMath::Abs(MaxRecoilRotation.X), FMath::Abs(MaxRecoilRotation.X)); + BackEndRecoilTarget.SetRotation(curRot.Quaternion()); + + bHasActiveRecoil = !BackEndRecoilTarget.Equals(FTransform::Identity); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp new file mode 100644 index 0000000..3560b6e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp @@ -0,0 +1,136 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_InteractibleSettings.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_InteractibleSettings) + +#include "Components/PrimitiveComponent.h" +#include "GripMotionControllerComponent.h" +#include "GameFramework/Actor.h" + +UGS_InteractibleSettings::UGS_InteractibleSettings(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; +} + +void UGS_InteractibleSettings::OnBeginPlay_Implementation(UObject * CallingOwner) +{ + if (InteractionSettings.bGetInitialPositionsOnBeginPlay) + { + FTransform parentTrans = GetParentTransform(!InteractionSettings.bLimitsInLocalSpace); + + InteractionSettings.InitialAngularTranslation = parentTrans.Rotator(); + InteractionSettings.InitialLinearTranslation = parentTrans.GetTranslation(); + } +} +void UGS_InteractibleSettings::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + if (InteractionSettings.bIgnoreHandRotation && !InteractionSettings.bHasValidBaseTransform) + { + RemoveRelativeRotation(GrippingController, GripInformation); + } + +} + +void UGS_InteractibleSettings::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + InteractionSettings.bHasValidBaseTransform = false; +} + +void UGS_InteractibleSettings::RemoveRelativeRotation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + InteractionSettings.BaseTransform = GripInformation.RelativeTransform; + + // Reconstitute the controller transform relative to the object, then remove the rotation and set it back to relative to controller + // This could likely be done easier by just removing rotation that the object doesn't possess but for now this will do. + FTransform compTrans = this->GetParentTransform(true, GripInformation.GrippedBoneName); + + InteractionSettings.BaseTransform = FTransform(InteractionSettings.BaseTransform.ToInverseMatrixWithScale()) * compTrans; // Reconstitute transform + InteractionSettings.BaseTransform.SetScale3D(GrippingController->GetPivotTransform().GetScale3D()); + InteractionSettings.BaseTransform.SetRotation(FQuat::Identity); // Remove rotation + + InteractionSettings.BaseTransform = compTrans.GetRelativeTransform(InteractionSettings.BaseTransform); // Set back to relative + InteractionSettings.bHasValidBaseTransform = true; +} + +bool UGS_InteractibleSettings::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!root) + return false; + + FTransform LocalTransform; + + if (InteractionSettings.bIgnoreHandRotation) + { + if (!InteractionSettings.bHasValidBaseTransform) + { + // Removes the rotation portion of the relative grip transform + RemoveRelativeRotation(GrippingController, Grip); + } + + FTransform RotationalessTransform = ParentTransform; + RotationalessTransform.SetRotation(FQuat::Identity); + + WorldTransform = InteractionSettings.BaseTransform * Grip.AdditionTransform * RotationalessTransform; + } + else + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + + if (InteractionSettings.bLimitsInLocalSpace) + { + if (USceneComponent * parent = root->GetAttachParent()) + LocalTransform = parent->GetComponentTransform(); + else + LocalTransform = FTransform::Identity; + + WorldTransform = WorldTransform.GetRelativeTransform(LocalTransform); + } + + FVector componentLoc = WorldTransform.GetLocation(); + + // Translation settings + if (InteractionSettings.bLimitX) + componentLoc.X = FMath::Clamp(componentLoc.X, InteractionSettings.InitialLinearTranslation.X + InteractionSettings.MinLinearTranslation.X, InteractionSettings.InitialLinearTranslation.X + InteractionSettings.MaxLinearTranslation.X); + + if (InteractionSettings.bLimitY) + componentLoc.Y = FMath::Clamp(componentLoc.Y, InteractionSettings.InitialLinearTranslation.Y + InteractionSettings.MinLinearTranslation.Y, InteractionSettings.InitialLinearTranslation.Y + InteractionSettings.MaxLinearTranslation.Y); + + if (InteractionSettings.bLimitZ) + componentLoc.Z = FMath::Clamp(componentLoc.Z, InteractionSettings.InitialLinearTranslation.Z + InteractionSettings.MinLinearTranslation.Z, InteractionSettings.InitialLinearTranslation.Z + InteractionSettings.MaxLinearTranslation.Z); + + WorldTransform.SetLocation(componentLoc); + + FRotator componentRot = WorldTransform.GetRotation().Rotator(); + + // Rotation Settings + if (InteractionSettings.bLimitPitch) + componentRot.Pitch = FMath::Clamp(componentRot.Pitch, InteractionSettings.InitialAngularTranslation.Pitch + InteractionSettings.MinAngularTranslation.Pitch, InteractionSettings.InitialAngularTranslation.Pitch + InteractionSettings.MaxAngularTranslation.Pitch); + + if (InteractionSettings.bLimitYaw) + componentRot.Yaw = FMath::Clamp(componentRot.Yaw, InteractionSettings.InitialAngularTranslation.Yaw + InteractionSettings.MinAngularTranslation.Yaw, InteractionSettings.InitialAngularTranslation.Yaw + InteractionSettings.MaxAngularTranslation.Yaw); + + if (InteractionSettings.bLimitRoll) + componentRot.Roll = FMath::Clamp(componentRot.Roll, InteractionSettings.InitialAngularTranslation.Roll + InteractionSettings.MinAngularTranslation.Roll, InteractionSettings.InitialAngularTranslation.Roll + InteractionSettings.MaxAngularTranslation.Roll); + + WorldTransform.SetRotation(componentRot.Quaternion()); + + if (InteractionSettings.bLimitsInLocalSpace) + { + WorldTransform = WorldTransform * LocalTransform; + } + + return true; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp new file mode 100644 index 0000000..6555498 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp @@ -0,0 +1,193 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GripScripts/GS_LerpToHand.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_LerpToHand) + +#include "GripMotionControllerComponent.h" +#include "VRGlobalSettings.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "Math/DualQuat.h" + +UGS_LerpToHand::UGS_LerpToHand(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = false; + bDenyAutoDrop = true; // Always deny auto dropping while this script is active + WorldTransformOverrideType = EGSTransformOverrideType::ModifiesWorldTransform; + + LerpInterpolationMode = EVRLerpInterpolationMode::QuatInterp; + LerpDuration = 1.f; + LerpSpeed = 0.0f; + CurrentLerpTime = 0.0f; + OnGripTransform = FTransform::Identity; + bUseCurve = false; + MinDistanceForLerp = 0.0f; + MinSpeedForLerp = 0.f; + MaxSpeedForLerp = 0.f; + TargetGrip = INVALID_VRGRIP_ID; +} + +//void UGS_InteractibleSettings::BeginPlay_Implementation() {} +void UGS_LerpToHand::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + // Removed this, let per object scripts overide + // Dont run if the global lerping is enabled + /*if (VRSettings.bUseGlobalLerpToHand) + { + bIsActive = false; + return; + }*/ + + TargetGrip = GripInformation.GripID; + + OnGripTransform = GetParentTransform(true, GripInformation.GrippedBoneName); + UObject* ParentObj = this->GetParent(); + + FTransform TargetTransform = GripInformation.RelativeTransform * GrippingController->GetPivotTransform(); + float Distance = FVector::Dist(OnGripTransform.GetLocation(), TargetTransform.GetLocation()); + if (MinDistanceForLerp > 0.0f && Distance < MinDistanceForLerp) + { + // Don't init + OnLerpToHandFinished.Broadcast(); + return; + } + else + { + float LerpScaler = 1.0f; + float DistanceToSpeed = Distance / LerpDuration; + if (DistanceToSpeed < MinSpeedForLerp) + { + LerpScaler = MinSpeedForLerp / DistanceToSpeed; + } + else if (MaxSpeedForLerp > 0.f && DistanceToSpeed > MaxSpeedForLerp) + { + LerpScaler = MaxSpeedForLerp / DistanceToSpeed; + } + else + { + LerpScaler = 1.0f; + } + + // Get the modified lerp speed + LerpSpeed = ((1.f / LerpDuration) * LerpScaler); + + OnLerpToHandBegin.Broadcast(); + + if (FBPActorGripInformation* GripInfo = GrippingController->GetGripPtrByID(GripInformation.GripID)) + { + GripInfo->bIsLerping = true; + } + } + + + + bIsActive = true; + CurrentLerpTime = 0.0f; +} + +void UGS_LerpToHand::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if(GripInformation.GripID == TargetGrip) + { + TargetGrip = INVALID_VRGRIP_ID; + bIsActive = false; + } +} + +bool UGS_LerpToHand::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!root) + return false; + + if (LerpDuration <= 0.f || !Grip.bIsLerping) + { + Grip.bIsLerping = false; + GrippingController->OnLerpToHandFinished.Broadcast(Grip); + bIsActive = false; + } + + FTransform NA = OnGripTransform;//root->GetComponentTransform(); + + float Alpha = 0.0f; + + CurrentLerpTime += DeltaTime * LerpSpeed; + float OrigAlpha = FMath::Clamp(CurrentLerpTime, 0.f, 1.0f); + Alpha = OrigAlpha; + + if (bUseCurve) + { + if (FRichCurve * richCurve = OptionalCurveToFollow.GetRichCurve()) + { + /*if (CurrentLerpTime > richCurve->GetLastKey().Time) + { + // Stop lerping + OnLerpToHandFinished.Broadcast(); + CurrentLerpTime = 0.0f; + bIsActive = false; + return true; + } + else*/ + { + Alpha = FMath::Clamp(richCurve->Eval(Alpha), 0.f, 1.f); + //CurrentLerpTime += DeltaTime; + } + } + } + + FTransform NB = WorldTransform; + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + if (LerpInterpolationMode == EVRLerpInterpolationMode::QuatInterp) + { + WorldTransform.Blend(NA, NB, Alpha); + } + + // Euler Angle interpolation + else if (LerpInterpolationMode == EVRLerpInterpolationMode::EulerInterp) + { + WorldTransform.SetTranslation(FMath::Lerp(NA.GetTranslation(), NB.GetTranslation(), Alpha)); + WorldTransform.SetScale3D(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + + FRotator A = NA.Rotator(); + FRotator B = NB.Rotator(); + WorldTransform.SetRotation(FQuat(A + (Alpha * (B - A)))); + } + // Dual quaternion interpolation + else + { + if ((NB.GetRotation() | NA.GetRotation()) < 0.0f) + { + NB.SetRotation(NB.GetRotation()*-1.0f); + } + WorldTransform = (FDualQuat(NA)*(1 - Alpha) + FDualQuat(NB)*Alpha).Normalized().AsFTransform(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + } + + // Turn it off if we need to + if (OrigAlpha == 1.0f) + { + OnLerpToHandFinished.Broadcast(); + Grip.bIsLerping = false; + GrippingController->OnLerpToHandFinished.Broadcast(Grip); + CurrentLerpTime = 0.0f; + bIsActive = false; + } + + return true; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp new file mode 100644 index 0000000..bb7d926 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp @@ -0,0 +1,972 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Melee.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_Melee) + +#include "VRGripInterface.h" +#include "GameFramework/WorldSettings.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "PhysicsEngine/PhysicsConstraintActor.h" +#include "PhysicsEngine/PhysicsConstraintComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +#include "VRGlobalSettings.h" +#include "DrawDebugHelpers.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GripMotionControllerComponent.h" + +UGS_Melee::UGS_Melee(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::ModifiesWorldTransform; + bDenyLateUpdates = true; + + + bInjectPrePhysicsHandle = true; + bInjectPostPhysicsHandle = true; + WeaponRootOrientationComponent = NAME_None; + OrientationComponentRelativeFacing = FTransform::Identity; + + bAutoSetPrimaryAndSecondaryHands = true; + PrimaryHandSelectionType = EVRMeleePrimaryHandType::VRPHAND_Rear; + bHasValidPrimaryHand = false; + + //RollingVelocityAverage = FVector::ZeroVector; + bIsLodged = false; + + //bCanEverTick = true; + bCheckLodge = false; + bIsHeld = false; + bCanEverTick = false; + bAlwaysTickPenetration = false; + bUsePrimaryHandSettingsWithOneHand = false; + COMType = EVRMeleeComType::VRPMELEECOM_BetweenHands; + bOnlyPenetrateWithTwoHands = false; +} + +void UGS_Melee::UpdateDualHandInfo() +{ + TArray HoldingControllers; + + bool bIsHeldOther; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeldOther); + + float PHand = 0.0f; + float SHand = 0.0f; + bHasValidPrimaryHand = false; + + FBPActorGripInformation* FrontHandGrip = nullptr; + FBPActorGripInformation* RearHandGrip = nullptr; + + SecondaryHand = FBPGripPair(); + PrimaryHand = FBPGripPair(); + + int NumControllers = HoldingControllers.Num(); + + for (FBPGripPair& Grip : HoldingControllers) + { + if (NumControllers > 1) + { + FBPActorGripInformation* GripInfo = Grip.HoldingController->GetGripPtrByID(Grip.GripID); + if (GripInfo) + { + float GripDistanceOnPrimaryAxis = 0.f; + FTransform relTransform(GripInfo->RelativeTransform.ToInverseMatrixWithScale()); + relTransform = relTransform.GetRelativeTransform(OrientationComponentRelativeFacing); + + // This is the Forward vector projected transform + // The most negative one of these is the rearmost hand + FVector localLoc = relTransform.GetTranslation(); + + switch (PrimaryHandSelectionType) + { + case EVRMeleePrimaryHandType::VRPHAND_Slotted: + { + if (GripInfo->bIsSlotGrip) + { + PrimaryHand = Grip; + bHasValidPrimaryHand = true; + } + else + { + if (!PrimaryHand.IsValid()) + { + PrimaryHand = Grip; + } + + SecondaryHand = Grip; + } + }break; + case EVRMeleePrimaryHandType::VRPHAND_Front: + case EVRMeleePrimaryHandType::VRPHAND_Rear: + { + + if (((PrimaryHandSelectionType == EVRMeleePrimaryHandType::VRPHAND_Rear) ? localLoc.X < PHand : localLoc.X > PHand) || !PrimaryHand.HoldingController) + { + PrimaryHand = Grip; + PHand = localLoc.X; + bHasValidPrimaryHand = true; + } + + if ((((PrimaryHandSelectionType == EVRMeleePrimaryHandType::VRPHAND_Rear) ? localLoc.X > SHand : localLoc.X < SHand) || !SecondaryHand.HoldingController || SecondaryHand.HoldingController == PrimaryHand.HoldingController)) + { + SecondaryHand = Grip; + SHand = localLoc.X; + } + + }break; + default:break; + } + } + } + else + { + PrimaryHand = Grip; + SecondaryHand = FBPGripPair(); + } + } + + + if (PrimaryHand.IsValid() && (COMType == EVRMeleeComType::VRPMELEECOM_BetweenHands || COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand)) + { + FBPActorGripInformation* GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + if (SecondaryHand.IsValid()) + { + FBPActorGripInformation* GripInfoS = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + + if (GripInfo && GripInfoS) + { + FVector Primary = GripInfo->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + FVector Secondary = GripInfoS->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + + FVector Final = (COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand) ? Primary : ((Primary + Secondary) / 2.f); + ObjectRelativeGripCenter.SetLocation(Final); + } + } + else + { + if (GripInfo) + { + + if (GripInfo->SecondaryGripInfo.bHasSecondaryAttachment) + { + FVector gripLoc = GripInfo->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + FVector secGripLoc = GripInfo->SecondaryGripInfo.SecondaryRelativeTransform.GetLocation(); + FVector finalloc = (COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand) ? gripLoc : (gripLoc + secGripLoc) / 2.f; + FVector finalScaled = finalloc * GripInfo->RelativeTransform.GetScale3D(); + + FTransform ownerTrans = GetOwner()->GetActorTransform(); + + //DrawDebugSphere(GetWorld(), ownerTrans.TransformPosition(finalScaled), 4.0f, 32, FColor::Orange, true); + + + ObjectRelativeGripCenter.SetLocation(finalScaled); + PrimaryHand.HoldingController->ReCreateGrip(*GripInfo); + } + else + { + ObjectRelativeGripCenter = FTransform::Identity; + } + } + } + } +} + +void UGS_Melee::UpdateHandPositionAndRotation(FBPGripPair HandPair, FTransform HandWorldTransform, FVector& LocDifference, float& RotDifference, bool bUpdateLocation, bool bUpdateRotation) +{ + LocDifference = FVector::ZeroVector; + + if (HandPair.IsValid()) + { + FBPActorGripInformation* GripInfo = HandPair.HoldingController->GetGripPtrByID(HandPair.GripID); + + if (GripInfo) + { + // Make hand relative to object transform + FTransform RelativeTrans = GripInfo->RelativeTransform.Inverse(); + FVector OriginalLoc = RelativeTrans.GetLocation(); + FQuat OriginalRot = RelativeTrans.GetRotation(); + + // Get our current parent transform + FTransform ParentTransform = GetParentTransform(); + + FQuat orientationRot = OrientationComponentRelativeFacing.GetRotation(); + + if (bUpdateLocation) + { + FVector currentRelVec = orientationRot.RotateVector(ParentTransform.InverseTransformPosition(HandWorldTransform.GetLocation())); + FVector currentLoc = orientationRot.RotateVector(RelativeTrans.GetLocation()); + currentLoc.X = currentRelVec.X; + RelativeTrans.SetLocation(orientationRot.UnrotateVector(currentLoc)); + } + + if (bUpdateRotation) + { + FRotator currentRelRot = (orientationRot * (ParentTransform.GetRotation().Inverse() * HandWorldTransform.GetRotation())).Rotator(); + FRotator currentRot = (orientationRot * RelativeTrans.GetRotation()).Rotator(); + currentRot.Roll = currentRelRot.Roll; + RelativeTrans.SetRotation(orientationRot.Inverse() * currentRot.Quaternion()); + } + + GripInfo->RelativeTransform = RelativeTrans.Inverse(); + HandPair.HoldingController->UpdatePhysicsHandle(*GripInfo, true); + HandPair.HoldingController->NotifyGripTransformChanged(*GripInfo); + + LocDifference = RelativeTrans.GetLocation() - OriginalLoc; + RotDifference = RelativeTrans.GetRotation().Rotator().Roll - OriginalRot.Rotator().Roll; + + // Instead of recreating, can directly set local pose here + + + FBPGripPair SecHand = SecondaryHand; + UpdateDualHandInfo(); + + if (SecondaryHand.IsValid() && !(SecHand == SecondaryHand)) + { + + GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(HandPair.HoldingController, HandPair.HoldingController->GetPhysicsGrip(HandPair.GripID)); + } + } +} + +void UGS_Melee::UpdateHandPosition(FBPGripPair HandPair, FVector HandWorldPosition, FVector& LocDifference) +{ + LocDifference = FVector::ZeroVector; + + if (HandPair.IsValid()) + { + FBPActorGripInformation* GripInfo = HandPair.HoldingController->GetGripPtrByID(HandPair.GripID); + + if (GripInfo) + { + // Make hand relative to object transform + FTransform RelativeTrans = GripInfo->RelativeTransform.Inverse(); + FVector OriginalLoc = RelativeTrans.GetLocation(); + + // Get our current parent transform + FTransform ParentTransform = GetParentTransform(); + + FQuat orientationRot = OrientationComponentRelativeFacing.GetRotation(); + FVector currentRelVec = orientationRot.RotateVector(ParentTransform.InverseTransformPosition(HandWorldPosition)); + //currentRelVec = OrientationComponentRelativeFacing.GetRotation().UnrotateVector(currentRelVec); + + FVector currentLoc = orientationRot.RotateVector(RelativeTrans.GetLocation()); + currentLoc.X = currentRelVec.X; + + RelativeTrans.SetLocation(orientationRot.UnrotateVector(currentLoc)); + GripInfo->RelativeTransform = RelativeTrans.Inverse(); + HandPair.HoldingController->UpdatePhysicsHandle(*GripInfo, true); + HandPair.HoldingController->NotifyGripTransformChanged(*GripInfo); + + LocDifference = RelativeTrans.GetLocation() - OriginalLoc; + + // Instead of recreating, can directly set local pose here + + + FBPGripPair SecHand = SecondaryHand; + UpdateDualHandInfo(); + + if (SecondaryHand.IsValid() && !(SecHand == SecondaryHand)) + { + + GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(HandPair.HoldingController, HandPair.HoldingController->GetPhysicsGrip(HandPair.GripID)); + } + } +} + +void UGS_Melee::SetPrimaryAndSecondaryHands(FBPGripPair& PrimaryGrip, FBPGripPair& SecondaryGrip) +{ + PrimaryHand = PrimaryGrip; + SecondaryHand = SecondaryGrip; +} + +void UGS_Melee::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* Controller, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + UpdateDualHandInfo(); +} + +void UGS_Melee::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + // Not storing an id, we should only be doing this once +// GetOwner()->OnActorHit.AddDynamic(this, &UGS_Melee::OnActorHit); + + // This lets us change the grip settings prior to actually starting the grip off + //SetTickEnabled(true); + //bCheckLodge = true; + bIsHeld = true; + + //if (GrippingController->HasGripAuthority(GripInformation)) + { + UpdateDualHandInfo(); + + // If we have multiple hands then alter the grip settings here for what we have already, the other will wait until post event + if (SecondaryHand.IsValid()) + { + FBPActorGripInformation * GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + } +} + +void UGS_Melee::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + + if (!bIsActive) + return; + + // Refresh our IsHeld var + TArray HoldingControllers; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeld); + + //if(!bAlwaysTickPenetration) + //SetTickEnabled(false); + + //if (!bAlwaysTickPenetration) + // bCheckLodge = false; + + if (SecondaryHand.IsValid() && SecondaryHand.HoldingController == ReleasingController && SecondaryHand.GripID == GripInformation.GripID) + { + SecondaryHand = FBPGripPair(); + } + else if (PrimaryHand.IsValid() && PrimaryHand.HoldingController == ReleasingController && PrimaryHand.GripID == GripInformation.GripID) + { + if (SecondaryHand.IsValid()) + { + PrimaryHand = SecondaryHand; + SecondaryHand = FBPGripPair(); + } + else + { + PrimaryHand = FBPGripPair(); + } + } + + + if (PrimaryHand.IsValid()) + { + + FBPActorPhysicsHandleInformation* HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + FBPActorGripInformation * GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + if (GripInfo) + { + //Reset defaults here still!!! + HandleInfo->LinConstraint.XDrive.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive.bEnableVelocityDrive = true; + HandleInfo->LinConstraint.XDrive.Stiffness = GripInfo->Stiffness; + HandleInfo->LinConstraint.XDrive.Damping = GripInfo->Damping; + + HandleInfo->LinConstraint.YDrive = HandleInfo->LinConstraint.XDrive; + HandleInfo->LinConstraint.ZDrive = HandleInfo->LinConstraint.XDrive; + + HandleInfo->AngConstraint.SwingDrive.bEnablePositionDrive = false; + HandleInfo->AngConstraint.SwingDrive.bEnableVelocityDrive = false; + HandleInfo->AngConstraint.TwistDrive.bEnablePositionDrive = false; + HandleInfo->AngConstraint.TwistDrive.bEnableVelocityDrive = false; + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.SlerpDrive.bEnablePositionDrive = true; + HandleInfo->AngConstraint.SlerpDrive.bEnableVelocityDrive = true; + + if (GripInfo->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && GripInfo->AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + HandleInfo->AngConstraint.SlerpDrive.Damping = GripInfo->AdvancedGripSettings.PhysicsSettings.AngularDamping; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = GripInfo->AdvancedGripSettings.PhysicsSettings.AngularStiffness; + } + else + { + HandleInfo->AngConstraint.SlerpDrive.Damping = GripInfo->Damping * 1.4f; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = GripInfo->Stiffness * 1.5f; + } + + FBPAdvGripSettings AdvSettings = IVRGripInterface::Execute_AdvancedGripSettings(GripInfo->GrippedObject); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = AdvSettings.PhysicsSettings.PhysicsGripLocationSettings; + + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + } + +} + +void UGS_Melee::OnBeginPlay_Implementation(UObject * CallingOwner) +{ + // Grip base has no super of this + + if (AActor * Owner = GetOwner()) + { + FName CurrentCompName = NAME_None; + bool bSearchRootComp = WeaponRootOrientationComponent.IsValid(); + int RemainingCount = PenetrationNotifierComponents.Num(); + for (UActorComponent* ChildComp : Owner->GetComponents()) + { + CurrentCompName = ChildComp->GetFName(); + if (CurrentCompName == NAME_None) + continue; + + if (bSearchRootComp && CurrentCompName == WeaponRootOrientationComponent) + { + bSearchRootComp = false; + if (USceneComponent * SceneComp = Cast(ChildComp)) + { + OrientationComponentRelativeFacing = SceneComp->GetRelativeTransform(); + } + } + + if (FBPLodgeComponentInfo * Found = PenetrationNotifierComponents.FindByKey(CurrentCompName)) + { + if (UPrimitiveComponent * PrimComp = Cast(ChildComp)) + { + Found->TargetComponent = TObjectPtr(PrimComp); + //PrimComp->OnComponentHit.AddDynamic(this, &UGS_Melee::OnLodgeHitCallback); + } + + // Decrement even if it failed the cast, they just had it wrong. + RemainingCount--; + } + + if (!bSearchRootComp && RemainingCount < 1) + { + break; + } + } + + // If we found at least one penetration object + if (RemainingCount < PenetrationNotifierComponents.Num()) + { + Owner->OnActorHit.AddDynamic(this, &UGS_Melee::OnLodgeHitCallback); + bCheckLodge = true; + } + } +} + +void UGS_Melee::OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) +{ + if (AActor * Owner = GetOwner()) + { + Owner->OnActorHit.RemoveDynamic(this, &UGS_Melee::OnLodgeHitCallback); + } +} + +void UGS_Melee::OnLodgeHitCallback(AActor* SelfActor, AActor* OtherActor, FVector NormalImpulse, const FHitResult& Hit) +{ + if (!Hit.GetComponent()) + return; + + if (!bCheckLodge || !bIsActive || bIsLodged || OtherActor == SelfActor) + { + if (bAlwaysTickPenetration || bIsHeld) + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + } + return; + } + + // Escape out if we are not held and are not set to always tick penetration + if (!bAlwaysTickPenetration && !bIsHeld) + return; + + TArray AllowedPenetrationSurfaceTypes; + + if (OverrideMeleeSurfaceSettings.Num() > 0) + { + // Use our local copy + AllowedPenetrationSurfaceTypes = OverrideMeleeSurfaceSettings; + } + else + { + // Use the global settings + UVRGlobalSettings::GetMeleeSurfaceGlobalSettings(AllowedPenetrationSurfaceTypes); + } + + FBPHitSurfaceProperties HitSurfaceProperties; + if (Hit.PhysMaterial.IsValid()) + { + HitSurfaceProperties.SurfaceType = Hit.PhysMaterial->SurfaceType; + } + + if (AllowedPenetrationSurfaceTypes.Num()) + { + // Reject bad surface types + if (!Hit.PhysMaterial.IsValid()) + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + return; + } + + EPhysicalSurface PhysSurfaceType = Hit.PhysMaterial->SurfaceType; + int32 IndexOfSurface = AllowedPenetrationSurfaceTypes.IndexOfByPredicate([&PhysSurfaceType](const FBPHitSurfaceProperties& Entry) { return Entry.SurfaceType == PhysSurfaceType; }); + + if (IndexOfSurface != INDEX_NONE) + { + HitSurfaceProperties = AllowedPenetrationSurfaceTypes[IndexOfSurface]; + } + else + { + // Surface is not part of our default list, don't allow penetration + HitSurfaceProperties.bSurfaceAllowsPenetration = false; + // Not a valid surface type to throw an event + //return; + } + } + + /*if (UPrimitiveComponent * root = Cast(SelfActor->GetRootComponent())) + { + if (FBodyInstance * rBodyInstance = root->GetBodyInstance()) + { + //float mass =rBodyInstance->GetBodyMass(); + //ImpulseVelocity = (NormalImpulse / rBodyInstance->GetBodyMass()).SizeSquared(); + RollingVelocityAverage += FVector::CrossProduct(RollingAngVelocityAverage, Hit.ImpactPoint - (rBodyInstance->GetCOMPosition())); + } + }*/ + +// FVector FrameToFrameVelocity = RollingVelocityAverage; + + // If we hit, then regardless of penetration, end the velocity history and reset +// RollingVelocityAverage = FVector::ZeroVector; +// RollingAngVelocityAverage = FVector::ZeroVector; + + bool bHadFirstHit = false; + FBPLodgeComponentInfo FirstHitComp; + + float HitNormalImpulse = NormalImpulse.SizeSquared(); + + for(FBPLodgeComponentInfo &LodgeData : PenetrationNotifierComponents) + { + if (!IsValid(LodgeData.TargetComponent)) + continue; + + FBox LodgeLocalBox = LodgeData.TargetComponent->CalcLocalBounds().GetBox(); + FVector LocalHit = LodgeData.TargetComponent->GetComponentTransform().InverseTransformPosition(Hit.ImpactPoint); + //FBox LodgeBox = LodgeData.TargetComponent->Bounds.GetBox(); + if (IsValid(LodgeData.TargetComponent) && LodgeLocalBox.IsInsideOrOn(LocalHit))//LodgeBox.IsInsideOrOn(Hit.ImpactPoint)) + { + FVector ForwardVec = LodgeData.TargetComponent->GetForwardVector(); + + // Using swept objects hit normal as we are looking for a facing from ourselves + float DotValue = FMath::Abs(FVector::DotProduct(Hit.Normal, ForwardVec)); + float Velocity = NormalImpulse.ProjectOnToNormal(ForwardVec).SizeSquared();//FrameToFrameVelocity.ProjectOnToNormal(ForwardVec); + // Check if the velocity was strong enough along our axis to count as a lodge event + // Also that our facing was in the relatively correct direction + + if (HitSurfaceProperties.bSurfaceAllowsPenetration && (!bOnlyPenetrateWithTwoHands || SecondaryHand.IsValid())) + { + if (LodgeData.ZoneType != EVRMeleeZoneType::VRPMELLE_ZONETYPE_Hit && DotValue >= (1.0f - LodgeData.AcceptableForwardProductRange) && (Velocity * HitSurfaceProperties.StabVelocityScaler) >= FMath::Square(LodgeData.PenetrationVelocity)) + { + OnShouldLodgeInObject.Broadcast(LodgeData, OtherActor, Hit.GetComponent(), Hit.GetComponent()->GetCollisionObjectType(), HitSurfaceProperties, NormalImpulse, Hit); + return; + //break; + } + } + + float HitImpulse = LodgeData.bIgnoreForwardVectorForHitImpulse ? HitNormalImpulse : Velocity; + + if (!bHadFirstHit && LodgeData.ZoneType > EVRMeleeZoneType::VRPMELLE_ZONETYPE_Stab && DotValue >= (1.0f - LodgeData.AcceptableForwardProductRangeForHits) && HitImpulse >= FMath::Square(LodgeData.MinimumHitVelocity)) + { + bHadFirstHit = true; + FirstHitComp = LodgeData; + } + } + } + + if (bHadFirstHit) + { + OnMeleeHit.Broadcast(FirstHitComp, OtherActor, Hit.GetComponent(), Hit.GetComponent()->GetCollisionObjectType(), HitSurfaceProperties, NormalImpulse, Hit); + } + else + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + } +} + + +void UGS_Melee::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + // This moves the kinematic actor to face its X+ in the direction designated + KinPose.SetRotation(KinPose.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + } +} + +void UGS_Melee::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + if (!bIsActive) + return; + + if (SecondaryHand.IsValid() )// && GrippingController == PrimaryHand.HoldingController) + { + if (GrippingController == SecondaryHand.HoldingController && HandleInfo->GripID == SecondaryHand.GripID) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + else if (GrippingController == PrimaryHand.HoldingController && HandleInfo->GripID == PrimaryHand.GripID) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(GrippingController, HandleInfo); + } + else + { + if (bUsePrimaryHandSettingsWithOneHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + + //HandleInfo->bSetCOM = false; // Should i remove this? + HandleInfo->bSkipResettingCom = false; + } +} + +void UGS_Melee::SetComBetweenHands(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + + if (!GrippingController || !HandleInfo) + return; + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal && SecondaryHand.IsValid()) + { + //if (PrimaryHand.HoldingController == GrippingController) + { + if (UPrimitiveComponent * PrimComp = Cast(GetParentSceneComp())) + { + if (FBodyInstance * rBodyInstance = PrimComp->GetBodyInstance()) + { + FPhysicsCommand::ExecuteWrite(rBodyInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + localCom.SetLocation((HandleInfo->RootBoneRotation * ObjectRelativeGripCenter).GetLocation()); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + + }); + } + } + } + + HandleInfo->bSetCOM = true; // Should i remove this? + HandleInfo->bSkipResettingCom = true; + } +} + +/*void UGS_Melee::Tick(float DeltaTime) +{ + AActor* myOwner = GetOwner(); + + if (UPrimitiveComponent * root = Cast(myOwner->GetRootComponent())) + { + FBodyInstance* rBodyInstance = root->GetBodyInstance(); + if (rBodyInstance && rBodyInstance->IsValidBodyInstance()) + { + RollingVelocityAverage = rBodyInstance->GetUnrealWorldVelocity(); + RollingAngVelocityAverage = rBodyInstance->GetUnrealWorldAngularVelocityInRadians(); + } + } +}*/ + +bool UGS_Melee::Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller) +{ + /*if (PrimaryHand.IsValid() && Controller != PrimaryHand.HoldingController) + { + return true; + }*/ + + return false; +} + +bool UGS_Melee::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + // Just simple transform setting + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + WorldTransform.SetLocation((GrippingController->GetPivotLocation() + Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation()) / 2.f); + } + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + /*if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && + Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::ConstantLerp_DEPRECATED; + } + else*/ + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); // Get our pivot point + const FTransform PivotToWorld = FTransform(FQuat::Identity, BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + frontLoc = Grip.SecondaryGripInfo.LastRelativeLocation; + + frontLocOrig = FMath::Lerp(frontLoc, frontLocOrig, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + } + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripDistanceInfluence_DEPRECATED) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDeadZone_DEPRECATED) / FMath::Max(Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDistanceToZero_DEPRECATED, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + }*/ + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + + + // Fixup X rotation to keep it aligned with the primary hand. + //WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Get primary hand relative position + FTransform InverseTrans(Grip.RelativeTransform.ToInverseMatrixWithScale()); + + // Get the original location of the hand + FVector origLocation = InverseTrans.GetLocation(); + + FVector orientedvector = FVector::VectorPlaneProject(origLocation, -OrientationComponentRelativeFacing.GetRotation().GetForwardVector()); + FVector newLocation = FVector::VectorPlaneProject(WorldTransform.InverseTransformPosition(GrippingController->GetPivotLocation()), OrientationComponentRelativeFacing.GetRotation().GetForwardVector()); + + FQuat DeltaQuat = FQuat::FindBetweenVectors(orientedvector, newLocation); + + WorldTransform.SetRotation(DeltaQuat * WorldTransform.GetRotation()); + } + + return true; +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp new file mode 100644 index 0000000..c954563 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp @@ -0,0 +1,111 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Physics.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GS_Physics) + +#include "VRGripInterface.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GripMotionControllerComponent.h" + +UGS_Physics::UGS_Physics(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::None; + + bDenyLateUpdates = true; + + bInjectPrePhysicsHandle = false; + bInjectPostPhysicsHandle = true; + + bCanEverTick = false; + + SingleHandPhysicsSettings.TwistSettings.MaxForceCoefficient = 1.f; + SingleHandPhysicsSettings.SwingSettings = SingleHandPhysicsSettings.TwistSettings; + SingleHandPhysicsSettings.SlerpSettings = SingleHandPhysicsSettings.TwistSettings; + + SingleHandPhysicsSettings.XAxisSettings.MaxForceCoefficient = 1.f; + SingleHandPhysicsSettings.YAxisSettings = SingleHandPhysicsSettings.XAxisSettings; + SingleHandPhysicsSettings.ZAxisSettings = SingleHandPhysicsSettings.XAxisSettings; + +} + +void UGS_Physics::UpdateDualHandInfo(UGripMotionControllerComponent* GrippingController, bool bReCreate) +{ + TArray HoldingControllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeld); + + int NumControllers = HoldingControllers.Num(); + + for (FBPGripPair& Grip : HoldingControllers) + { + if (NumControllers > 1) + { + FBPActorPhysicsHandleInformation* HandleInfo = Grip.HoldingController->GetPhysicsGrip(Grip.GripID); + if (HandleInfo) + { + MultiHandPhysicsSettings.FillTo(HandleInfo); + + if(bReCreate && Grip.HoldingController != GrippingController) + Grip.HoldingController->UpdatePhysicsHandle(Grip.GripID, true); + } + } + else + { + FBPActorPhysicsHandleInformation* HandleInfo = Grip.HoldingController->GetPhysicsGrip(Grip.GripID); + if (HandleInfo) + { + SingleHandPhysicsSettings.FillTo(HandleInfo); + + if(bReCreate) + Grip.HoldingController->UpdatePhysicsHandle(Grip.GripID, true); + } + } + } +} + + +void UGS_Physics::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + UpdateDualHandInfo(GrippingController, true); +} + +void UGS_Physics::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + + if (!bIsActive) + return; + + UpdateDualHandInfo(nullptr, true); +} + +// Should I be orienting it to the controller for these types of grips? +/*void UGS_Physics::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + // This moves the kinematic actor to face its X+ in the direction designated + KinPose.SetRotation(KinPose.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + } +}*/ + +void UGS_Physics::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + if (!bIsActive) + return; + + + UpdateDualHandInfo(GrippingController, false); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp new file mode 100644 index 0000000..fdb052d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp @@ -0,0 +1,357 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/VRGripScriptBase.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRGripScriptBase) + +#include "GripMotionControllerComponent.h" +#include "VRGripInterface.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" +#include "Engine/NetDriver.h" + + +UVRGripScriptBase::UVRGripScriptBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +// PrimaryComponentTick.bCanEverTick = false; +// PrimaryComponentTick.bStartWithTickEnabled = false; +// PrimaryComponentTick.TickGroup = ETickingGroup::TG_PrePhysics; + WorldTransformOverrideType = EGSTransformOverrideType::None; + bDenyAutoDrop = false; + bDenyLateUpdates = false; + bForceDrop = false; + bIsActive = false; + + bCanEverTick = false; + bAllowTicking = false; +} + +void UVRGripScriptBase::OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) {}; +void UVRGripScriptBase::OnBeginPlay_Implementation(UObject * CallingOwner) {}; + +bool UVRGripScriptBase::GetWorldTransform_Implementation(UGripMotionControllerComponent* GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) { return true; } +void UVRGripScriptBase::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRGripScriptBase::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRGripScriptBase::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRGripScriptBase::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} + + +EGSTransformOverrideType UVRGripScriptBase::GetWorldTransformOverrideType() { return WorldTransformOverrideType; } +bool UVRGripScriptBase::IsScriptActive() { return bIsActive; } +//bool UVRGripScriptBase::Wants_DenyAutoDrop() { return bDenyAutoDrop; } +//bool UVRGripScriptBase::Wants_DenyLateUpdates() { return bDenyLateUpdates; } +//bool UVRGripScriptBase::Wants_ToForceDrop() { return bForceDrop; } +bool UVRGripScriptBase::Wants_DenyTeleport_Implementation(UGripMotionControllerComponent * Controller) { return false; } +void UVRGripScriptBase::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) {} +void UVRGripScriptBase::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) {} + +UVRGripScriptBase* UVRGripScriptBase::GetGripScriptByClass(UObject* WorldContextObject, TSubclassOf GripScriptClass, EBPVRResultSwitch& Result) +{ + if (WorldContextObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(WorldContextObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsA(GripScriptClass)) + { + Result = EBPVRResultSwitch::OnSucceeded; + return Script; + } + } + } + } + + Result = EBPVRResultSwitch::OnFailed; + return nullptr; +} + +void UVRGripScriptBase::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + // Uobject has no replicated props + //Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Replicate here if required + UBlueprintGeneratedClass* BPClass = Cast(GetClass()); + if (BPClass != NULL) + { + BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps); + } +} + +void UVRGripScriptBase::Tick(float DeltaTime) +{ + // Do nothing by default +} + +bool UVRGripScriptBase::IsTickable() const +{ + return bAllowTicking; +} + +UWorld* UVRGripScriptBase::GetTickableGameObjectWorld() const +{ + return GetWorld(); +} + +bool UVRGripScriptBase::IsTickableInEditor() const +{ + return false; +} + +bool UVRGripScriptBase::IsTickableWhenPaused() const +{ + return false; +} + +ETickableTickType UVRGripScriptBase::GetTickableTickType() const +{ + if(IsTemplate(RF_ClassDefaultObject)) + return ETickableTickType::Never; + + return bCanEverTick ? ETickableTickType::Conditional : ETickableTickType::Never; +} + +TStatId UVRGripScriptBase::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT(UVRGripScriptBase, STATGROUP_Tickables); +} + +void UVRGripScriptBase::SetTickEnabled(bool bTickEnabled) +{ + bAllowTicking = bTickEnabled; +} + + +// Not currently compiling in editor builds....not entirely sure why... +/* +void UVRGripScriptBase::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + + // In the grippables pre replication to pass it on +#ifndef WITH_EDITOR + // Run pre-replication for any grip scripts + if (GripLogicScripts.Num()) + { + if (UNetDriver* NetDriver = GetNetDriver()) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + Script->PreReplication(*((IRepChangedPropertyTracker *)NetDriver->FindOrCreateRepChangedPropertyTracker(Script).Get())); + } + } + } + } +#endif + + UBlueprintGeneratedClass* BPClass = Cast(GetClass()); + if (BPClass != NULL) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } +}*/ + +bool UVRGripScriptBase::CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack) +{ + bool bProcessed = false; + + if (AActor* MyOwner = GetOwner()) + { + FWorldContext* const Context = GEngine->GetWorldContextFromWorld(GetWorld()); + if (Context != nullptr) + { + for (FNamedNetDriver& Driver : Context->ActiveNetDrivers) + { + if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateFunction(MyOwner, Function)) + { + Driver.NetDriver->ProcessRemoteFunction(MyOwner, Function, Parms, OutParms, Stack, this); + + bProcessed = true; + } + } + } + } + + return bProcessed; +} + +int32 UVRGripScriptBase::GetFunctionCallspace(UFunction * Function, FFrame * Stack) +{ + AActor* Owner = GetOwner(); + + if (HasAnyFlags(RF_ClassDefaultObject) || !IsSupportedForNetworking() || !Owner) + { + // This handles absorbing authority/cosmetic + return GEngine->GetGlobalFunctionCallspace(Function, this, Stack); + } + + // Owner is certified valid now + return Owner->GetFunctionCallspace(Function, Stack); +} + +FTransform UVRGripScriptBase::GetGripTransform(const FBPActorGripInformation &Grip, const FTransform & ParentTransform) +{ + return Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; +} + +USceneComponent * UVRGripScriptBase::GetParentSceneComp() +{ + UObject* ParentObj = this->GetParent(); + + if (USceneComponent * PrimParent = Cast(ParentObj)) + { + return PrimParent; + } + else if (AActor * ParentActor = Cast(ParentObj)) + { + return ParentActor->GetRootComponent(); + } + + return nullptr; +} + +FTransform UVRGripScriptBase::GetParentTransform(bool bGetWorldTransform, FName BoneName) +{ + UObject* ParentObj = this->GetParent(); + + if (USceneComponent* PrimParent = Cast(ParentObj)) + { + if (BoneName != NAME_None) + { + return PrimParent->GetSocketTransform(BoneName); + } + else + { + return PrimParent->GetComponentTransform(); + } + } + else if (AActor* ParentActor = Cast(ParentObj)) + { + return ParentActor->GetActorTransform(); + } + + return FTransform::Identity; +} + +FBodyInstance * UVRGripScriptBase::GetParentBodyInstance(FName OptionalBoneName) +{ + UObject * ParentObj = this->GetParent(); + + if (UPrimitiveComponent * PrimParent = Cast(ParentObj)) + { + return PrimParent->GetBodyInstance(OptionalBoneName); + } + else if (AActor * ParentActor = Cast(ParentObj)) + { + if (UPrimitiveComponent * Prim = Cast(ParentActor->GetRootComponent())) + { + return Prim->GetBodyInstance(OptionalBoneName); + } + } + + return nullptr; +} + +UObject * UVRGripScriptBase::GetParent() +{ + return this->GetOuter(); +} + +AActor * UVRGripScriptBase::GetOwner() +{ + UObject * myOuter = this->GetOuter(); + + if (!myOuter) + return nullptr; + + if (AActor * ActorOwner = Cast(myOuter)) + { + return ActorOwner; + } + else if (UActorComponent * ComponentOwner = Cast(myOuter)) + { + return ComponentOwner->GetOwner(); + } + + return nullptr; +} + +bool UVRGripScriptBase::HasAuthority() +{ + if (AActor * MyOwner = GetOwner()) + { + return MyOwner->GetLocalRole() == ROLE_Authority; + } + + return false; +} + +bool UVRGripScriptBase::IsServer() +{ + if (AActor * MyOwner = GetOwner()) + { + return MyOwner->GetNetMode() < ENetMode::NM_Client; + } + + return false; +} + +UWorld* UVRGripScriptBase::GetWorld() const +{ + if (IsTemplate()) + return nullptr; + + if (GIsEditor && !GIsPlayInEditorWorld) + { + return nullptr; + } + else if (UObject * Outer = GetOuter()) + { + return Outer->GetWorld(); + } + + return nullptr; +} + +void UVRGripScriptBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + OnEndPlay(EndPlayReason); +} + +void UVRGripScriptBase::BeginPlay(UObject * CallingOwner) +{ + if (bAlreadyNotifiedPlay) + return; + + bAlreadyNotifiedPlay = true; + + // Notify the subscripts about begin play + OnBeginPlay(CallingOwner); +} + +void UVRGripScriptBase::PostInitProperties() +{ + Super::PostInitProperties(); + + //Called in game, when World exist . BeginPlay will not be called in editor + if (GetWorld()) + { + if (AActor* Owner = GetOwner()) + { + if (Owner->IsActorInitialized()) + { + BeginPlay(GetOwner()); + } + } + } +} + +void UVRGripScriptBaseBP::Tick(float DeltaTime) +{ + ReceiveTick(DeltaTime); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp new file mode 100644 index 0000000..d572da3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp @@ -0,0 +1,825 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableActor.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableActor) + +#include "TimerManager.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "GripScripts/VRGripScriptBase.h" +#include "DrawDebugHelpers.h" + +#include "Physics/Experimental/PhysScene_Chaos.h" +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + + + //============================================================================= +AGrippableActor::AGrippableActor(const FObjectInitializer& ObjectInitializer) + : Super() +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bAllowIgnoringAttachOnOwner = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableActor::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableActor, AttachmentWeldReplication, this); + } +#endif + + /*PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS*/ +} + +void AGrippableActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { +#if UE_WITH_IRIS + const bool bPrevRepPhysics = GetReplicatedMovement_Mutable().bRepPhysics; +#endif // UE_WITH_IRIS + + bool bFoundInCache = false; + + UWorld* World = GetWorld(); + int ServerFrame = 0; + if (FPhysScene_Chaos* Scene = static_cast(World->GetPhysicsScene())) + { + if (const FRigidBodyState* FoundState = Scene->GetStateFromReplicationCache(RootPrimComp, ServerFrame)) + { + RepMovement.FillFrom(*FoundState, this, Scene->ReplicationCache.ServerFrame); + bFoundInCache = true; + } + } + + if (!bFoundInCache) + { + // fallback to GT data + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + RepMovement.FillFrom(RBState, this, 0); + } + + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + +#if UE_WITH_IRIS + // If RepPhysics has changed value then notify the ReplicationSystem + if (bPrevRepPhysics != GetReplicatedMovement_Mutable().bRepPhysics) + { + UpdateReplicatePhysicsCondition(); + } +#endif // UE_WITH_IRIS + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParentActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } + +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableActor, AttachmentWeldReplication, this); + } +#endif + } +} + +void AGrippableActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +bool AGrippableActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +AGrippableActor::~AGrippableActor() +{ +} + +void AGrippableActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + + +void AGrippableActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableActor::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void AGrippableActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableActor::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void AGrippableActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void AGrippableActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableActor::OnUsed_Implementation() {} +void AGrippableActor::OnEndUsed_Implementation() {} +void AGrippableActor::OnSecondaryUsed_Implementation() {} +void AGrippableActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableActor::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool AGrippableActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableActor::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableActor::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld * World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableActor::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +bool AGrippableActor::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +/*FBPInteractionSettings AGrippableActor::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool AGrippableActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld* OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent * PrimComp = Cast(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor* tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController * PlayerController = Cast(TopOwner)) + { + if (APlayerState * PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableActor::CeaseReplicationBlocking() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld * OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + if (UPrimitiveComponent* RootPrim = Cast(GetRootComponent())) + { + PhysicsReplication->RemoveReplicatedTarget(RootPrim); + } + } + } + } +} + +bool AGrippableActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR & newMovement) +{ + return true; +} + +void AGrippableActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR & newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} +// This isn't called very many places but it does come up +void AGrippableActor::MarkComponentsAsGarbage(bool bModify) +{ + Super::MarkComponentsAsGarbage(bModify); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +/** Called right before being marked for destruction due to network replication */ +// Clean up our objects so that they aren't sitting around for GC +void AGrippableActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent * ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +// On Destroy clean up our objects +void AGrippableActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableActor::GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp new file mode 100644 index 0000000..9cc70c6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp @@ -0,0 +1,323 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableBoxComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableBoxComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + +//============================================================================= +UGrippableBoxComponent::~UGrippableBoxComponent() +{ +} + + //============================================================================= +UGrippableBoxComponent::UGrippableBoxComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; +} + +void UGrippableBoxComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableBoxComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableBoxComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableBoxComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, GameplayTags, COND_Custom); +} + +void UGrippableBoxComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableBoxComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableBoxComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableBoxComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableBoxComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + + +void UGrippableBoxComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableBoxComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableBoxComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableBoxComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableBoxComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableBoxComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableBoxComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableBoxComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableBoxComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableBoxComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableBoxComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableBoxComponent::OnUsed_Implementation() {} +void UGrippableBoxComponent::OnEndUsed_Implementation() {} +void UGrippableBoxComponent::OnSecondaryUsed_Implementation() {} +void UGrippableBoxComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableBoxComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableBoxComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableBoxComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableBoxComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableBoxComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableBoxComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableBoxComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableBoxComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableBoxComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableBoxComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableBoxComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableBoxComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableBoxComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableBoxComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableBoxComponent::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableBoxComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableBoxComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableBoxComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableBoxComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableBoxComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableBoxComponent::GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +// This one is for components to clean up +void UGrippableBoxComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp new file mode 100644 index 0000000..3fe4901 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp @@ -0,0 +1,323 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableCapsuleComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableCapsuleComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableCapsuleComponent::UGrippableCapsuleComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; +} + +void UGrippableCapsuleComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableCapsuleComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableCapsuleComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableCapsuleComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, GameplayTags, COND_Custom); +} + +void UGrippableCapsuleComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableCapsuleComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableCapsuleComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableCapsuleComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableCapsuleComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableCapsuleComponent::~UGrippableCapsuleComponent() +{ +} + +void UGrippableCapsuleComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableCapsuleComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableCapsuleComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableCapsuleComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableCapsuleComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableCapsuleComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableCapsuleComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableCapsuleComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableCapsuleComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableCapsuleComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableCapsuleComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableCapsuleComponent::OnUsed_Implementation() {} +void UGrippableCapsuleComponent::OnEndUsed_Implementation() {} +void UGrippableCapsuleComponent::OnSecondaryUsed_Implementation() {} +void UGrippableCapsuleComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableCapsuleComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableCapsuleComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + + +bool UGrippableCapsuleComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableCapsuleComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableCapsuleComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableCapsuleComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableCapsuleComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableCapsuleComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableCapsuleComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableCapsuleComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableCapsuleComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableCapsuleComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableCapsuleComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableCapsuleComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableCapsuleComponent::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableCapsuleComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableCapsuleComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableCapsuleComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableCapsuleComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableCapsuleComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableCapsuleComponent::GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableCapsuleComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp new file mode 100644 index 0000000..7750958 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp @@ -0,0 +1,31 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableCharacter.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableCharacter) + +#include "VRGlobalSettings.h" +#include "Grippables/GrippableSkeletalMeshComponent.h" + + +AGrippableCharacter::AGrippableCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::MeshComponentName, UVRGlobalSettings::GetDefaultGrippableCharacterMeshComponentClass())) +{ + ViewOriginationSocket = NAME_None; + GrippableMeshReference = Cast(GetMesh()); +} + +void AGrippableCharacter::GetActorEyesViewPoint(FVector& Location, FRotator& Rotation) const +{ + if (ViewOriginationSocket != NAME_None) + { + if (USkeletalMeshComponent* MyMesh = GetMesh()) + { + FTransform SocketTransform = MyMesh->GetSocketTransform(ViewOriginationSocket, RTS_World); + Location = SocketTransform.GetLocation(); + Rotation = SocketTransform.GetRotation().Rotator(); + return; + } + } + + Super::GetActorEyesViewPoint(Location, Rotation); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableDataTypes.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableDataTypes.cpp new file mode 100644 index 0000000..c6be104 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableDataTypes.cpp @@ -0,0 +1,25 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableDataTypes.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableDataTypes) + +#include "GameFramework/Actor.h" + + +bool FRepAttachmentWithWeld::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + // Our additional weld bit is here + Ar.SerializeBits(&bIsWelded, 1); + Ar << AttachParent; + LocationOffset.NetSerialize(Ar, Map, bOutSuccess); + RelativeScale3D.NetSerialize(Ar, Map, bOutSuccess); + RotationOffset.SerializeCompressedShort(Ar); + Ar << AttachSocket; + Ar << AttachComponent; + return true; +} + +FRepAttachmentWithWeld::FRepAttachmentWithWeld() +{ + bIsWelded = false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp new file mode 100644 index 0000000..284a52a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp @@ -0,0 +1,1578 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Grippables/GrippablePhysicsReplication.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippablePhysicsReplication) + +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "DrawDebugHelpers.h" +#include "Chaos/ChaosMarshallingManager.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "Chaos/PhysicsObjectInternalInterface.h" +#include "Chaos/PhysicsObject.h" +#include "PBDRigidsSolver.h" +#include "Chaos/PBDRigidsEvolutionGBF.h" +#include "VRGlobalSettings.h" +#include "PhysicsEngine/PhysicsSettings.h" +#include "Physics/Experimental/PhysScene_Chaos.h" +#include "Chaos/DebugDrawQueue.h" +//#include "Components/SkeletalMeshComponent.h" +#include "Misc/ScopeRWLock.h" + +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "Engine/Player.h" +//#include "PhysicsInterfaceTypesCore.h" + +// I cannot dynamic cast without RTTI so I am using a static var as a declarative in case the user removed our custom replicator +// We don't want our casts to cause issues. +namespace VRPhysicsReplicationStatics +{ + static bool bHasVRPhysicsReplication = false; +} + +/*struct FAsyncPhysicsRepCallbackDataVR : public Chaos::FSimCallbackInput +{ + TArray Buffer; + ErrorCorrectionData ErrorCorrection; + + void Reset() + { + Buffer.Reset(); + } +};*/ + +/*class FPhysicsReplicationAsyncCallbackVR final : public Chaos::TSimCallbackObject +{ + virtual void OnPreSimulate_Internal() override + { + FPhysicsReplicationVR::ApplyAsyncDesiredStateVR(GetDeltaTime_Internal(), GetConsumerInput_Internal()); + } +};*/ + +FPhysicsReplicationVR::~FPhysicsReplicationVR() +{ + if (PhysicsReplicationAsyncVR) + { + if (auto* Solver = PhysSceneVR->GetSolver()) + { + Solver->UnregisterAndFreeSimCallbackObject_External(PhysicsReplicationAsyncVR); + } + } +} + + +void ComputeDeltasVR(const FVector& CurrentPos, const FQuat& CurrentQuat, const FVector& TargetPos, const FQuat& TargetQuat, FVector& OutLinDiff, float& OutLinDiffSize, + FVector& OutAngDiffAxis, float& OutAngDiff, float& OutAngDiffSize) +{ + OutLinDiff = TargetPos - CurrentPos; + OutLinDiffSize = OutLinDiff.Size(); + const FQuat InvCurrentQuat = CurrentQuat.Inverse(); + const FQuat DeltaQuat = InvCurrentQuat * TargetQuat; + DeltaQuat.ToAxisAndAngle(OutAngDiffAxis, OutAngDiff); + OutAngDiff = FMath::RadiansToDegrees(FMath::UnwindRadians(OutAngDiff)); + OutAngDiffSize = FMath::Abs(OutAngDiff); +} + +FPhysicsReplicationVR::FPhysicsReplicationVR(FPhysScene* PhysScene) : + FPhysicsReplication(PhysScene) +{ + PhysSceneVR = PhysScene; + AsyncInputVR = nullptr; + PhysicsReplicationAsyncVR = nullptr; + if (auto* Solver = PhysSceneVR->GetSolver()) + { + PhysicsReplicationAsyncVR = Solver->CreateAndRegisterSimCallbackObject_External(); + PhysicsReplicationAsyncVR->Setup(UPhysicsSettings::Get()->PhysicErrorCorrection); + } + + //CurAsyncDataVR = nullptr; + //AsyncCallbackServer = nullptr; + + VRPhysicsReplicationStatics::bHasVRPhysicsReplication = true; +} + +void FPhysicsReplicationVR::PrepareAsyncData_ExternalVR(const FRigidBodyErrorCorrection& ErrorCorrection) +{ + //todo move this logic into a common function? + static const auto CVarLinSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PositionLerp")); + const float PositionLerp = CVarLinSet->GetFloat() >= 0.0f ? CVarLinSet->GetFloat() : ErrorCorrection.PositionLerp; + + static const auto CVarLinLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.LinearVelocityCoefficient")); + const float LinearVelocityCoefficient = CVarLinLerp->GetFloat() >= 0.0f ? CVarLinLerp->GetFloat() : ErrorCorrection.LinearVelocityCoefficient; + + static const auto CVarAngSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngleLerp")); + const float AngleLerp = CVarAngSet->GetFloat() >= 0.0f ? CVarAngSet->GetFloat() : ErrorCorrection.AngleLerp; + + static const auto CVarAngLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngularVelocityCoefficient")); + const float AngularVelocityCoefficient = CVarAngLerp->GetFloat() >= 0.0f ? CVarAngLerp->GetFloat() : ErrorCorrection.AngularVelocityCoefficient; + + AsyncInputVR = PhysicsReplicationAsyncVR->GetProducerInputData_External(); + AsyncInputVR->ErrorCorrection.PositionLerp = PositionLerp; + AsyncInputVR->ErrorCorrection.AngleLerp = AngleLerp; + AsyncInputVR->ErrorCorrection.LinearVelocityCoefficient = LinearVelocityCoefficient; + AsyncInputVR->ErrorCorrection.AngularVelocityCoefficient = AngularVelocityCoefficient; +} + +/*void FPhysicsReplicationVR::ApplyAsyncDesiredStateVR(const float DeltaSeconds, const FAsyncPhysicsRepCallbackDataVR* AsyncData) +{ + using namespace Chaos; + if (AsyncData) + { + for (const FAsyncPhysicsDesiredState& State : AsyncData->Buffer) + { + float LinearVelocityCoefficient = AsyncData->ErrorCorrection.LinearVelocityCoefficient; + float AngularVelocityCoefficient = AsyncData->ErrorCorrection.AngularVelocityCoefficient; + float PositionLerp = AsyncData->ErrorCorrection.PositionLerp; + float AngleLerp = AsyncData->ErrorCorrection.AngleLerp; + if (State.ErrorCorrection.IsSet()) + { + ErrorCorrectionData ECData = State.ErrorCorrection.GetValue(); + LinearVelocityCoefficient = ECData.LinearVelocityCoefficient; + AngularVelocityCoefficient = ECData.AngularVelocityCoefficient; + PositionLerp = ECData.PositionLerp; + AngleLerp = ECData.AngleLerp; + } + + //Proxy should exist because we are using latest and any pending deletes would have been enqueued after + Chaos::FSingleParticlePhysicsProxy* Proxy = State.Proxy; + auto* Handle = Proxy->GetPhysicsThreadAPI(); + + + if (Handle && Handle->CanTreatAsRigid()) + { + const FVector TargetPos = State.WorldTM.GetLocation(); + const FQuat TargetQuat = State.WorldTM.GetRotation(); + + // Get Current state + FRigidBodyState CurrentState; + CurrentState.Position = Handle->X(); + CurrentState.Quaternion = Handle->R(); + CurrentState.AngVel = Handle->W(); + CurrentState.LinVel = Handle->V(); + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + float AngDiffSize; + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + const FVector NewLinVel = FVector(State.LinearVelocity) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(State.AngularVelocity) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), TargetPos, PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + Handle->SetX(NewPos); + Handle->SetR(NewAng); + Handle->SetV(NewLinVel); + Handle->SetW(FMath::DegreesToRadians(NewAngVel)); + + if (State.bShouldSleep) + { + // don't allow kinematic to sleeping transition + if (Handle->ObjectState() != EObjectStateType::Kinematic) + { + auto* Solver = Proxy->GetSolver(); + Solver->GetEvolution()->SetParticleObjectState(Proxy->GetHandle_LowLevel()->CastToRigidParticle(), EObjectStateType::Sleeping); //todo: move object state into physics thread api + } + } + } + } + } +}*/ + + +bool FPhysicsReplicationVR::IsInitialized() +{ + return VRPhysicsReplicationStatics::bHasVRPhysicsReplication; +} + + +bool FPhysicsReplicationVR::ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float InPingSecondsOneWay, int32 LocalFrame, int32 NumPredictedFrames) +{ + + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, ErrorCorrection, InPingSecondsOneWay, LocalFrame, NumPredictedFrames); + } + } + + // Call into the old ApplyRigidBodyState function for now, + return ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, ErrorCorrection, InPingSecondsOneWay); +} + +void FPhysicsReplicationVR::SetReplicatedTarget(UPrimitiveComponent* Component, FName BoneName, const FRigidBodyState& ReplicatedTarget, int32 ServerFrame) +{ + + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::SetReplicatedTarget(Component, BoneName, ReplicatedTarget, ServerFrame); + } + } + + static const auto CVarEnableDefaultReplication = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.EnableDefaultReplication")); + + // If networked physics prediction is enabled, enforce the new physics replication flow via SetReplicatedTarget() using PhysicsObject instead of BodyInstance from BoneName. + AActor* Owner = Component->GetOwner(); + if (UPhysicsSettings::Get()->PhysicsPrediction.bEnablePhysicsPrediction && Owner && + (CVarEnableDefaultReplication->GetInt() || Owner->GetPhysicsReplicationMode() != EPhysicsReplicationMode::Default)) // For now, only opt in to the PhysicsObject flow if not using Default replication or if default is allowed via CVar. + { + const ENetRole OwnerRole = Owner->GetLocalRole(); + const bool bIsSimulated = OwnerRole == ROLE_SimulatedProxy; + const bool bIsReplicatedAutonomous = OwnerRole == ROLE_AutonomousProxy && Component->bReplicatePhysicsToAutonomousProxy; + if (bIsSimulated || bIsReplicatedAutonomous) + { + Chaos::FPhysicsObjectHandle PhysicsObject = Component->GetPhysicsObjectByName(BoneName); + SetReplicatedTargetVR(PhysicsObject, ReplicatedTarget, ServerFrame, Owner->GetPhysicsReplicationMode()); + return; + } + } + + return FPhysicsReplication::SetReplicatedTarget(Component, BoneName, ReplicatedTarget, ServerFrame); +} + +void FPhysicsReplicationVR::SetReplicatedTargetVR(Chaos::FPhysicsObject* PhysicsObject, const FRigidBodyState& ReplicatedTarget, int32 ServerFrame, EPhysicsReplicationMode ReplicationMode) +{ + UWorld* OwningWorld = GetOwningWorld(); + if (OwningWorld == nullptr) + { + return; + } + + // TODO, Check if owning actor is ROLE_SimulatedProxy or ROLE_AutonomousProxy ? + + FReplicatedPhysicsTarget Target; + Target.PhysicsObject = PhysicsObject; + Target.ReplicationMode = ReplicationMode; + Target.ServerFrame = ServerFrame; + Target.TargetState = ReplicatedTarget; + Target.ArrivedTimeSeconds = OwningWorld->GetTimeSeconds(); + + ensure(!Target.TargetState.Position.ContainsNaN()); + + ReplicatedTargetsQueueVR.Add(Target); +} + +bool FPhysicsReplicationVR::ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float PingSecondsOneWay, bool* bDidHardSnap) +{ + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, ErrorCorrection, PingSecondsOneWay); + } + } + + static const auto CVarSkipPhysicsReplication = IConsoleManager::Get().FindConsoleVariable(TEXT("p.SkipPhysicsReplication")); + if (CVarSkipPhysicsReplication->GetInt()) + { + return false; + } + + if (!BI->IsInstanceSimulatingPhysics()) + { + return false; + } + + // + // NOTES: + // + // The operation of this method has changed since 4.18. + // + // When a new remote physics state is received, this method will + // be called on tick until the local state is within an adequate + // tolerance of the new state. + // + // The received state is extrapolated based on ping, by some + // adjustable amount. + // + // A correction velocity is added new state's velocity, and assigned + // to the body. The correction velocity scales with the positional + // difference, so without the interference of external forces, this + // will result in an exponentially decaying correction. + // + // Generally it is not needed and will interrupt smoothness of + // the replication, but stronger corrections can be obtained by + // adjusting position lerping. + // + // If progress is not being made towards equilibrium, due to some + // divergence in physics states between the owning and local sims, + // an error value is accumulated, representing the amount of time + // spent in an unresolvable state. + // + // Once the error value has exceeded some threshold (0.5 seconds + // by default), a hard snap to the target physics state is applied. + // + + bool bRestoredState = true; + const FRigidBodyState NewState = PhysicsTarget.TargetState; + const float NewQuatSizeSqr = NewState.Quaternion.SizeSquared(); + + // failure cases + if (!BI->IsInstanceSimulatingPhysics()) + { + UE_LOG(LogPhysics, Warning, TEXT("Physics replicating on non-simulated body. (%s)"), *BI->GetBodyDebugName()); + return bRestoredState; + } + else if (NewQuatSizeSqr < UE_KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Invalid zero quaternion set for body. (%s)"), *BI->GetBodyDebugName()); + return bRestoredState; + } + else if (FMath::Abs(NewQuatSizeSqr - 1.f) > UE_KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Quaternion (%f %f %f %f) with non-unit magnitude detected. (%s)"), + NewState.Quaternion.X, NewState.Quaternion.Y, NewState.Quaternion.Z, NewState.Quaternion.W, *BI->GetBodyDebugName()); + return bRestoredState; + } + + // Grab configuration variables from engine config or from CVars if overriding is turned on. + static const auto CVarNetPingExtrapolation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingExtrapolation")); + const float NetPingExtrapolation = CVarNetPingExtrapolation->GetFloat() >= 0.0f ? CVarNetPingExtrapolation->GetFloat() : ErrorCorrection.PingExtrapolation; + + static const auto CVarNetPingLimit = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingLimit")); + const float NetPingLimit = CVarNetPingLimit->GetFloat() > 0.0f ? CVarNetPingLimit->GetFloat() : ErrorCorrection.PingLimit; + + static const auto CVarErrorPerLinearDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerLinearDifference")); + const float ErrorPerLinearDiff = CVarErrorPerLinearDifference->GetFloat() >= 0.0f ? CVarErrorPerLinearDifference->GetFloat() : ErrorCorrection.ErrorPerLinearDifference; + + static const auto CVarErrorPerAngularDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerAngularDifference")); + const float ErrorPerAngularDiff = CVarErrorPerAngularDifference->GetFloat() >= 0.0f ? CVarErrorPerAngularDifference->GetFloat() : ErrorCorrection.ErrorPerAngularDifference; + + static const auto CVarMaxRestoredStateError = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxRestoredStateError")); + const float MaxRestoredStateError = CVarMaxRestoredStateError->GetFloat() >= 0.0f ? CVarMaxRestoredStateError->GetFloat() : ErrorCorrection.MaxRestoredStateError; + + static const auto CVarErrorAccumulation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSeconds")); + const float ErrorAccumulationSeconds = CVarErrorAccumulation->GetFloat() >= 0.0f ? CVarErrorAccumulation->GetFloat() : ErrorCorrection.ErrorAccumulationSeconds; + + static const auto CVarErrorAccumulationDistanceSq = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationDistanceSq")); + const float ErrorAccumulationDistanceSq = CVarErrorAccumulationDistanceSq->GetFloat() >= 0.0f ? CVarErrorAccumulationDistanceSq->GetFloat() : ErrorCorrection.ErrorAccumulationDistanceSq; + + static const auto CVarErrorAccumulationSimilarity = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSimilarity")); + const float ErrorAccumulationSimilarity = CVarErrorAccumulationSimilarity->GetFloat() >= 0.0f ? CVarErrorAccumulationSimilarity->GetFloat() : ErrorCorrection.ErrorAccumulationSimilarity; + + static const auto CVarLinSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PositionLerp")); + const float PositionLerp = CVarLinSet->GetFloat() >= 0.0f ? CVarLinSet->GetFloat() : ErrorCorrection.PositionLerp; + + static const auto CVarLinLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.LinearVelocityCoefficient")); + const float LinearVelocityCoefficient = CVarLinLerp->GetFloat() >= 0.0f ? CVarLinLerp->GetFloat() : ErrorCorrection.LinearVelocityCoefficient; + + static const auto CVarAngSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngleLerp")); + const float AngleLerp = CVarAngSet->GetFloat() >= 0.0f ? CVarAngSet->GetFloat() : ErrorCorrection.AngleLerp; + + static const auto CVarAngLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngularVelocityCoefficient")); + const float AngularVelocityCoefficient = CVarAngLerp->GetFloat() >= 0.0f ? CVarAngLerp->GetFloat() : ErrorCorrection.AngularVelocityCoefficient; + + static const auto CVarMaxLinearHardSnapDistance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxLinearHardSnapDistance")); + const float MaxLinearHardSnapDistance = CVarMaxLinearHardSnapDistance->GetFloat() >= 0.f ? CVarMaxLinearHardSnapDistance->GetFloat() : ErrorCorrection.MaxLinearHardSnapDistance; + + // Get Current state + FRigidBodyState CurrentState; + BI->GetRigidBodyState(CurrentState); + + /////// EXTRAPOLATE APPROXIMATE TARGET VALUES /////// + + // Starting from the last known authoritative position, and + // extrapolate an approximation using the last known velocity + // and ping. + const float PingSeconds = FMath::Clamp(PingSecondsOneWay, 0.f, NetPingLimit); + const float ExtrapolationDeltaSeconds = PingSeconds * NetPingExtrapolation; + const FVector ExtrapolationDeltaPos = NewState.LinVel * ExtrapolationDeltaSeconds; + const FVector_NetQuantize100 TargetPos = NewState.Position + ExtrapolationDeltaPos; + float NewStateAngVel; + FVector NewStateAngVelAxis; + NewState.AngVel.FVector::ToDirectionAndLength(NewStateAngVelAxis, NewStateAngVel); + NewStateAngVel = FMath::DegreesToRadians(NewStateAngVel); + const FQuat ExtrapolationDeltaQuaternion = FQuat(NewStateAngVelAxis, NewStateAngVel * ExtrapolationDeltaSeconds); + FQuat TargetQuat = ExtrapolationDeltaQuaternion * NewState.Quaternion; + + /////// COMPUTE DIFFERENCES /////// + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + + float AngDiffSize; + + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + /////// ACCUMULATE ERROR IF NOT APPROACHING SOLUTION /////// + + // Store sleeping state + const bool bShouldSleep = (NewState.Flags & ERigidBodyFlags::Sleeping) != 0; + const bool bWasAwake = BI->IsInstanceAwake(); + const bool bAutoWake = false; + + const float Error = (LinDiffSize * ErrorPerLinearDiff) + (AngDiffSize * ErrorPerAngularDiff); + bRestoredState = Error < MaxRestoredStateError; + if (bRestoredState) + { + PhysicsTarget.AccumulatedErrorSeconds = 0.0f; + } + else + { + // + // The heuristic for error accumulation here is: + // 1. Did the physics tick from the previous step fail to + // move the body towards a resolved position? + // 2. Was the linear error in the same direction as the + // previous frame? + // 3. Is the linear error large enough to accumulate error? + // + // If these conditions are met, then "error" time will accumulate. + // Once error has accumulated for a certain number of seconds, + // a hard-snap to the target will be performed. + // + // TODO: Rotation while moving linearly can still mess up this + // heuristic. We need to account for it. + // + + // Project the change in position from the previous tick onto the + // linear error from the previous tick. This value roughly represents + // how much correction was performed over the previous physics tick. + const float PrevProgress = FVector::DotProduct( + FVector(CurrentState.Position) - PhysicsTarget.PrevPos, + (PhysicsTarget.PrevPosTarget - PhysicsTarget.PrevPos).GetSafeNormal()); + + // Project the current linear error onto the linear error from the + // previous tick. This value roughly represents how little the direction + // of the linear error state has changed, and how big the error is. + const float PrevSimilarity = FVector::DotProduct( + TargetPos - FVector(CurrentState.Position), + PhysicsTarget.PrevPosTarget - PhysicsTarget.PrevPos); + + // If the conditions from the heuristic outlined above are met, accumulate + // error. Otherwise, reduce it. + if (PrevProgress < ErrorAccumulationDistanceSq && + PrevSimilarity > ErrorAccumulationSimilarity) + { + PhysicsTarget.AccumulatedErrorSeconds += DeltaSeconds; + } + else + { + PhysicsTarget.AccumulatedErrorSeconds = FMath::Max(PhysicsTarget.AccumulatedErrorSeconds - DeltaSeconds, 0.0f); + } + + // Hard snap if error accumulation or linear error is big enough, and clear the error accumulator. + static const auto CVarAlwaysHardSnap = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AlwaysHardSnap")); + const bool bHardSnap = + LinDiffSize > MaxLinearHardSnapDistance || + PhysicsTarget.AccumulatedErrorSeconds > ErrorAccumulationSeconds || + CVarAlwaysHardSnap->GetInt(); + + const FTransform IdealWorldTM(TargetQuat, TargetPos); + + if (bHardSnap) + { +#if !UE_BUILD_SHIPPING + if (PhysicsReplicationCVars::LogPhysicsReplicationHardSnaps && GetOwningWorld()) + { + UE_LOG(LogTemp, Warning, TEXT("Simulated HARD SNAP - \nCurrent Pos - %s, Target Pos - %s\n CurrentState.LinVel - %s, New Lin Vel - %s\nTarget Extrapolation Delta - %s, Is Replay? - %d, Is Asleep - %d, Prev Progress - %f, Prev Similarity - %f"), + *CurrentState.Position.ToString(), *TargetPos.ToString(), *CurrentState.LinVel.ToString(), *NewState.LinVel.ToString(), + *ExtrapolationDeltaPos.ToString(), GetOwningWorld()->IsPlayingReplay(), !BI->IsInstanceAwake(), PrevProgress, PrevSimilarity); + if (bDidHardSnap) + { + *bDidHardSnap = true; + } + if (LinDiffSize > MaxLinearHardSnapDistance) + { + UE_LOG(LogTemp, Warning, TEXT("Hard snap due to linear difference error")); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Hard snap due to accumulated error")) + } + } +#endif + // Too much error so just snap state here and be done with it + PhysicsTarget.AccumulatedErrorSeconds = 0.0f; + bRestoredState = true; + BI->SetBodyTransform(IdealWorldTM, ETeleportType::ResetPhysics, bAutoWake); + + // Set the new velocities + BI->SetLinearVelocity(NewState.LinVel, false, bAutoWake); + BI->SetAngularVelocityInRadians(FMath::DegreesToRadians(NewState.AngVel), false, bAutoWake); + } + else + { + // Small enough error to interpolate + if (PhysicsReplicationAsyncVR == nullptr) //sync case + { + const FVector NewLinVel = FVector(NewState.LinVel) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(NewState.AngVel) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), FVector(TargetPos), PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + BI->SetBodyTransform(FTransform(NewAng, NewPos), ETeleportType::ResetPhysics); + BI->SetLinearVelocity(NewLinVel, false); + BI->SetAngularVelocityInRadians(FMath::DegreesToRadians(NewAngVel), false); + } + else + { + //If async is used, enqueue for callback + FPhysicsRepAsyncInputData AsyncInputData; + AsyncInputData.TargetState = NewState; + AsyncInputData.TargetState.Position = IdealWorldTM.GetLocation(); + AsyncInputData.TargetState.Quaternion = IdealWorldTM.GetRotation(); + AsyncInputData.Proxy = static_cast(BI->GetPhysicsActorHandle()); + AsyncInputData.PhysicsObject = nullptr; + AsyncInputData.ErrorCorrection = { ErrorCorrection.LinearVelocityCoefficient, ErrorCorrection.AngularVelocityCoefficient, ErrorCorrection.PositionLerp, ErrorCorrection.AngleLerp }; + + AsyncInputData.LatencyOneWay = PingSeconds; + + + AsyncInputVR->InputData.Add(AsyncInputData); + /*FAsyncPhysicsDesiredState AsyncDesiredState; + AsyncDesiredState.WorldTM = IdealWorldTM; + AsyncDesiredState.LinearVelocity = NewState.LinVel; + AsyncDesiredState.AngularVelocity = NewState.AngVel; + AsyncDesiredState.Proxy = static_cast(BI->GetPhysicsActorHandle()); + AsyncDesiredState.ErrorCorrection = { ErrorCorrection.LinearVelocityCoefficient, ErrorCorrection.AngularVelocityCoefficient, ErrorCorrection.PositionLerp, ErrorCorrection.AngleLerp }; + AsyncDesiredState.bShouldSleep = bShouldSleep; + CurAsyncDataVR->Buffer.Add(AsyncDesiredState);*/ + } + } + + // Should we show the async part? +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + if (CVarNetShowCorrections->GetInt() != 0) + { + PhysicsTarget.ErrorHistory.bAutoAdjustMinMax = false; + PhysicsTarget.ErrorHistory.MinValue = 0.0f; + PhysicsTarget.ErrorHistory.MaxValue = 1.0f; + PhysicsTarget.ErrorHistory.AddSample(PhysicsTarget.AccumulatedErrorSeconds / ErrorAccumulationSeconds); + if (UWorld* OwningWorld = GetOwningWorld()) + { + FColor Color = FColor::White; + static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime")); + DrawDebugDirectionalArrow(OwningWorld, CurrentState.Position, TargetPos, 5.0f, Color, true, CVarNetCorrectionLifetime->GetFloat(), 0, 1.5f); +#if 0 + //todo: do we show this in async mode? + DrawDebugFloatHistory(*OwningWorld, PhysicsTarget.ErrorHistory, NewPos + FVector(0.0f, 0.0f, 100.0f), FVector2D(100.0f, 50.0f), FColor::White); +#endif + } + } +#endif + } + + /////// SLEEP UPDATE /////// + + if (bShouldSleep) + { + // In the async case, we apply sleep state in ApplyAsyncDesiredState + if (PhysicsReplicationAsyncVR == nullptr) + { + BI->PutInstanceToSleep(); + } + } + + PhysicsTarget.PrevPosTarget = TargetPos; + PhysicsTarget.PrevPos = FVector(CurrentState.Position); + + return bRestoredState; +} + +void FPhysicsReplicationVR::OnTick(float DeltaSeconds, TMap, FReplicatedPhysicsTarget>& ComponentsToTargets) +{ + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::OnTick(DeltaSeconds, ComponentsToTargets); + } + } + + using namespace Chaos; + + if (ShouldSkipPhysicsReplication()) + { + return; + } + + using namespace Chaos; + + + int32 LocalFrameOffset = 0; // LocalFrame = ServerFrame + LocalFrameOffset; + if (FPhysicsSolverBase::IsNetworkPhysicsPredictionEnabled()) + { + if (UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == NM_Client) + { + if (APlayerController* PlayerController = World->GetFirstPlayerController()) + { + LocalFrameOffset = PlayerController->GetServerToLocalAsyncPhysicsTickOffset(); + } + } + } + } + + const FRigidBodyErrorCorrection& PhysicErrorCorrection = UPhysicsSettings::Get()->PhysicErrorCorrection; + if (PhysicsReplicationAsyncVR) + { + PrepareAsyncData_ExternalVR(PhysicErrorCorrection); + } + + // Get the ping between this PC & the server + const float LocalPing = 0.0f;//GetLocalPing(); + + for (auto Itr = ComponentsToTargets.CreateIterator(); Itr; ++Itr) + { + + bool bRemoveItr = false; + if (UPrimitiveComponent* PrimComp = Itr.Key().Get()) + { + if (FBodyInstance* BI = PrimComp->GetBodyInstance(Itr.Value().BoneName)) + { + FReplicatedPhysicsTarget& PhysicsTarget = Itr.Value(); + FRigidBodyState& UpdatedState = PhysicsTarget.TargetState; + bool bUpdated = false; + if (AActor* OwningActor = PrimComp->GetOwner()) + { + // Remove if there is no owner + if (!OwningActor->GetNetOwningPlayer()) + { + bRemoveItr = true; + } + else + { + // Removed as this is server sided + /*const ENetRole OwnerRole = OwningActor->GetLocalRole(); + const bool bIsSimulated = OwnerRole == ROLE_SimulatedProxy; + const bool bIsReplicatedAutonomous = OwnerRole == ROLE_AutonomousProxy && PrimComp->bReplicatePhysicsToAutonomousProxy; + if (bIsSimulated || bIsReplicatedAutonomous)*/ + + + // Deleted everything here, we will always be the server, I already filtered out clients to default logic + { + /*const*/ float OwnerPing = 0.0f;// GetOwnerPing(OwningActor, PhysicsTarget); + + /*if (UPlayer* OwningPlayer = OwningActor->GetNetOwningPlayer()) + { + if (APlayerController* PlayerController = OwningPlayer->GetPlayerController(nullptr)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + OwnerPing = PlayerState->ExactPing; + } + } + }*/ + + // Get the total ping - this approximates the time since the update was + // actually generated on the machine that is doing the authoritative sim. + // NOTE: We divide by 2 to approximate 1-way ping from 2-way ping. + const float PingSecondsOneWay = 0.0f;// (LocalPing + OwnerPing) * 0.5f * 0.001f; + + + if (UpdatedState.Flags & ERigidBodyFlags::NeedsUpdate) + { + const int32 LocalFrame = PhysicsTarget.ServerFrame + LocalFrameOffset; + const bool bRestoredState = ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, PhysicErrorCorrection, PingSecondsOneWay, LocalFrame, 0); + //const bool bRestoredState = ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, PhysicErrorCorrection, PingSecondsOneWay, LocalFrame, NumPredictedFrames); + + // Need to update the component to match new position. + static const auto CVarSkipSkeletalRepOptimization = IConsoleManager::Get().FindConsoleVariable(TEXT("p.SkipSkeletalRepOptimization")); + if (/*PhysicsReplicationCVars::SkipSkeletalRepOptimization*/CVarSkipSkeletalRepOptimization->GetInt() == 0 || Cast(PrimComp) == nullptr) //simulated skeletal mesh does its own polling of physics results so we don't need to call this as it'll happen at the end of the physics sim + { + PrimComp->SyncComponentToRBPhysics(); + } + + // Added a sleeping check from the input state as well, we always want to cease activity on sleep + if (bRestoredState /* || ((UpdatedState.Flags & ERigidBodyFlags::Sleeping) != 0)*/) + { + bRemoveItr = true; + } + } + } + } + } + } + + if (bRemoveItr) + { + OnTargetRestored(Itr.Key().Get(), Itr.Value()); + Itr.RemoveCurrent(); + } + } + } + + // PhysicsObject replication flow + for (FReplicatedPhysicsTarget& PhysicsTarget : ReplicatedTargetsQueueVR) + { + if (PhysicsTarget.TargetState.Flags & ERigidBodyFlags::NeedsUpdate) + { + const float PingSecondsOneWay = LocalPing * 0.5f * 0.001f; + + // Queue up the target state for async replication + FPhysicsRepAsyncInputData AsyncInputData; + AsyncInputData.TargetState = PhysicsTarget.TargetState; + AsyncInputData.Proxy = nullptr; + AsyncInputData.PhysicsObject = PhysicsTarget.PhysicsObject; + AsyncInputData.RepMode = PhysicsTarget.ReplicationMode; + AsyncInputData.ServerFrame = PhysicsTarget.ServerFrame; + AsyncInputData.FrameOffset = LocalFrameOffset; + AsyncInputData.LatencyOneWay = PingSecondsOneWay; + + AsyncInputVR->InputData.Add(AsyncInputData); + } + } + ReplicatedTargetsQueueVR.Reset(); + + AsyncInputVR = nullptr; +} + +FRepMovementVR::FRepMovementVR() : FRepMovement() +{ + LocationQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + VelocityQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + RotationQuantizationLevel = ERotatorQuantization::ShortComponents; +} + +FRepMovementVR::FRepMovementVR(FRepMovement& other) : FRepMovement() +{ + FRepMovementVR(); + + LinearVelocity = other.LinearVelocity; + AngularVelocity = other.AngularVelocity; + Location = other.Location; + Rotation = other.Rotation; + bSimulatedPhysicSleep = other.bSimulatedPhysicSleep; + bRepPhysics = other.bRepPhysics; +} + +void FRepMovementVR::CopyTo(FRepMovement& other) const +{ + other.LinearVelocity = LinearVelocity; + other.AngularVelocity = AngularVelocity; + other.Location = Location; + other.Rotation = Rotation; + other.bSimulatedPhysicSleep = bSimulatedPhysicSleep; + other.bRepPhysics = bRepPhysics; +} + +bool FRepMovementVR::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + return FRepMovement::NetSerialize(Ar, Map, bOutSuccess); +} + +bool FRepMovementVR::GatherActorsMovement(AActor* OwningActor) +{ + //if (/*bReplicateMovement || (RootComponent && RootComponent->GetAttachParent())*/) + { + UPrimitiveComponent* RootPrimComp = Cast(OwningActor->GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + + FillFrom(RBState, OwningActor); + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + bRepPhysics = !RootPrimComp->IsWelded(); + } + else if (RootPrimComp != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootPrimComp->GetAttachParent() != nullptr) + { + return false; // We don't handle attachment rep + + } + else + { + Location = FRepMovement::RebaseOntoZeroOrigin(RootPrimComp->GetComponentLocation(), OwningActor); + Rotation = RootPrimComp->GetComponentRotation(); + LinearVelocity = OwningActor->GetVelocity(); + AngularVelocity = FVector::ZeroVector; + } + + bRepPhysics = false; + } + } + + /*if (const UWorld* World = GetOwningWorld()) + { + if (APlayerController* PlayerController = World->GetFirstPlayerController()) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + CurrentPing = PlayerState->ExactPing; + } + } + }*/ + + return true; +} + +void FPhysicsReplicationAsyncVR::OnPreSimulate_Internal() +{ + if (const FPhysicsReplicationAsyncInput* AsyncInput = GetConsumerInput_Internal()) + { + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + check(RigidsSolver); + + // Early out if this is a resim frame + Chaos::FRewindData* RewindData = RigidsSolver->GetRewindData(); + if (RewindData && RewindData->IsResim()) + { + // TODO, Handle the transition from post-resim to interpolation better. + static const auto CVarPostResimWaitForUpdate = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.PostResimWaitForUpdate")); + if (CVarPostResimWaitForUpdate->GetBool() && RewindData->IsFinalResim()) + { + for (auto Itr = ObjectToTarget.CreateIterator(); Itr; ++Itr) + { + FReplicatedPhysicsTargetAsync& Target = Itr.Value(); + + // If final resim frame, mark interpolated targets as waiting for up to date data from the server. + if (Target.RepMode == EPhysicsReplicationMode::PredictiveInterpolation) + { + Target.bWaiting = true; + Target.ServerFrame = RigidsSolver->GetCurrentFrame() + Target.FrameOffset; + } + } + } + + return; + } + + // Update async targets with target input + for (const FPhysicsRepAsyncInputData& Input : AsyncInput->InputData) + { + UpdateRewindDataTarget(Input); + UpdateAsyncTarget(Input); + } + + ApplyTargetStatesAsync(GetDeltaTime_Internal(), AsyncInput->ErrorCorrection, AsyncInput->InputData); + } +} + +void FPhysicsReplicationAsyncVR::UpdateRewindDataTarget(const FPhysicsRepAsyncInputData& Input) +{ + if (Input.PhysicsObject == nullptr) + { + return; + } + + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + if (RigidsSolver == nullptr) + { + return; + } + + Chaos::FRewindData* RewindData = RigidsSolver->GetRewindData(); + if (RewindData == nullptr) + { + return; + } + + Chaos::FReadPhysicsObjectInterface_Internal Interface = Chaos::FPhysicsObjectInternalInterface::GetRead(); + Chaos::FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(Input.PhysicsObject); + + if (Handle != nullptr) + { + // Cache all target states inside RewindData + const int32 LocalFrame = Input.ServerFrame - Input.FrameOffset; + RewindData->SetTargetStateAtFrame(*Handle, LocalFrame, Chaos::FFrameAndPhase::EParticleHistoryPhase::PostPushData, + Input.TargetState.Position, Input.TargetState.Quaternion, + Input.TargetState.LinVel, Input.TargetState.AngVel, (Input.TargetState.Flags & ERigidBodyFlags::Sleeping)); + } +} + +void FPhysicsReplicationAsyncVR::UpdateAsyncTarget(const FPhysicsRepAsyncInputData& Input) +{ + if (Input.PhysicsObject == nullptr) + { + return; + } + + FReplicatedPhysicsTargetAsync* Target = ObjectToTarget.Find(Input.PhysicsObject); + if (Target == nullptr) + { + // First time we add a target, set it's previous and correction + // positions to the target position to avoid math with uninitialized + // memory. + Target = &ObjectToTarget.Add(Input.PhysicsObject); + Target->PrevPos = Input.TargetState.Position; + Target->PrevPosTarget = Input.TargetState.Position; + Target->PrevRotTarget = Input.TargetState.Quaternion; + Target->PrevLinVel = Input.TargetState.LinVel; + } + + if (Input.ServerFrame > Target->ServerFrame) + { + Target->PhysicsObject = Input.PhysicsObject; + Target->PrevServerFrame = Target->ServerFrame; + Target->ServerFrame = Input.ServerFrame; + Target->TargetState = Input.TargetState; + Target->RepMode = Input.RepMode; + Target->FrameOffset = Input.FrameOffset; + Target->TickCount = 0; + Target->bWaiting = false; + + if (Input.RepMode == EPhysicsReplicationMode::PredictiveInterpolation) + { + // Cache the position we received this target at, Predictive Interpolation will alter the target state but use this as the source position for reconciliation. + Target->PrevPosTarget = Input.TargetState.Position; + Target->PrevRotTarget = Input.TargetState.Quaternion; + } + } + + /** Cache the latest ping time */ + LatencyOneWay = Input.LatencyOneWay; +} + +void FPhysicsReplicationAsyncVR::ApplyTargetStatesAsync(const float DeltaSeconds, const FPhysicsRepErrorCorrectionData& ErrorCorrection, const TArray& InputData) +{ + using namespace Chaos; + + // Deprecated, BodyInstance flow + for (const FPhysicsRepAsyncInputData& Input : InputData) + { + if (Input.Proxy != nullptr) + { + Chaos::FSingleParticlePhysicsProxy* Proxy = Input.Proxy; + Chaos::FRigidBodyHandle_Internal* Handle = Proxy->GetPhysicsThreadAPI(); + + const FPhysicsRepErrorCorrectionData& UsedErrorCorrection = Input.ErrorCorrection.IsSet() ? Input.ErrorCorrection.GetValue() : ErrorCorrection; + DefaultReplication_DEPRECATED(Handle, Input, DeltaSeconds, UsedErrorCorrection); + } + } + + // PhysicsObject flow + for (auto Itr = ObjectToTarget.CreateIterator(); Itr; ++Itr) + { + bool bRemoveItr = true; // Remove current cached replication target unless replication logic tells us to store it for next tick + + FReplicatedPhysicsTargetAsync& Target = Itr.Value(); + + if (Target.PhysicsObject != nullptr) + { + Chaos::FWritePhysicsObjectInterface_Internal Interface = Chaos::FPhysicsObjectInternalInterface::GetWrite(); + FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(Target.PhysicsObject); + + if (Handle != nullptr) + { + // TODO, Remove the resim option from project settings, we only need the physics prediction one now + EPhysicsReplicationMode RepMode = Target.RepMode; + if (!Chaos::FPBDRigidsSolver::IsPhysicsResimulationEnabled() && RepMode == EPhysicsReplicationMode::Resimulation) + { + RepMode = EPhysicsReplicationMode::Default; + } + + switch (RepMode) + { + case EPhysicsReplicationMode::Default: + bRemoveItr = DefaultReplication(Handle, Target, DeltaSeconds); + break; + + case EPhysicsReplicationMode::PredictiveInterpolation: + bRemoveItr = PredictiveInterpolation(Handle, Target, DeltaSeconds); + break; + + case EPhysicsReplicationMode::Resimulation: + bRemoveItr = ResimulationReplication(Handle, Target, DeltaSeconds); + break; + } + } + } + + if (bRemoveItr) + { + Itr.RemoveCurrent(); + } + } +} + +//** Async function for legacy replication flow that goes partially through GT to then finishes in PT in this function. */ +void FPhysicsReplicationAsyncVR::DefaultReplication_DEPRECATED(Chaos::FRigidBodyHandle_Internal* Handle, const FPhysicsRepAsyncInputData& State, const float DeltaSeconds, const FPhysicsRepErrorCorrectionData& ErrorCorrection) +{ + if (Handle && Handle->CanTreatAsRigid()) + { + const float LinearVelocityCoefficient = ErrorCorrection.LinearVelocityCoefficient; + const float AngularVelocityCoefficient = ErrorCorrection.AngularVelocityCoefficient; + const float PositionLerp = ErrorCorrection.PositionLerp; + const float AngleLerp = ErrorCorrection.AngleLerp; + + const FVector TargetPos = State.TargetState.Position; + const FQuat TargetQuat = State.TargetState.Quaternion; + + // Get Current state + FRigidBodyState CurrentState; + CurrentState.Position = Handle->X(); + CurrentState.Quaternion = Handle->R(); + CurrentState.AngVel = Handle->W(); + CurrentState.LinVel = Handle->V(); + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + float AngDiffSize; + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + const FVector NewLinVel = FVector(State.TargetState.LinVel) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(State.TargetState.AngVel) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), TargetPos, PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + Handle->SetX(NewPos); + Handle->SetR(NewAng); + Handle->SetV(NewLinVel); + Handle->SetW(FMath::DegreesToRadians(NewAngVel)); + + if (State.TargetState.Flags & ERigidBodyFlags::Sleeping) + { + // don't allow kinematic to sleeping transition + if (Handle->ObjectState() != Chaos::EObjectStateType::Kinematic) + { + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + if (RigidsSolver) + { + RigidsSolver->GetEvolution()->SetParticleObjectState(Handle->GetProxy()->GetHandle_LowLevel()->CastToRigidParticle(), Chaos::EObjectStateType::Sleeping); //todo: move object state into physics thread api + } + } + } + } +} + + +/** Default replication, run in simulation tick */ +bool FPhysicsReplicationAsyncVR::DefaultReplication(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds) +{ + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + if (RigidsSolver == nullptr) + { + return true; + } + + // + // NOTES: + // + // The operation of this method has changed since 4.18. + // + // When a new remote physics state is received, this method will + // be called on tick until the local state is within an adequate + // tolerance of the new state. + // + // The received state is extrapolated based on ping, by some + // adjustable amount. + // + // A correction velocity is added new state's velocity, and assigned + // to the body. The correction velocity scales with the positional + // difference, so without the interference of external forces, this + // will result in an exponentially decaying correction. + // + // Generally it is not needed and will interrupt smoothness of + // the replication, but stronger corrections can be obtained by + // adjusting position lerping. + // + // If progress is not being made towards equilibrium, due to some + // divergence in physics states between the owning and local sims, + // an error value is accumulated, representing the amount of time + // spent in an unresolvable state. + // + // Once the error value has exceeded some threshold (0.5 seconds + // by default), a hard snap to the target physics state is applied. + // + + + bool bRestoredState = true; + const FRigidBodyState NewState = Target.TargetState; + const float NewQuatSizeSqr = NewState.Quaternion.SizeSquared(); + + + const FString ObjectName +#if CHAOS_DEBUG_NAME + = Handle->DebugName() ? *Handle->DebugName() : FString(TEXT("")); +#else + = FString(TEXT("")); +#endif + + // failure cases + if (Handle == nullptr) + { + UE_LOG(LogPhysics, Warning, TEXT("Trying to replicate rigid state for non-rigid particle. (%s)"), *ObjectName); + return bRestoredState; + } + else if (NewQuatSizeSqr < UE_KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Invalid zero quaternion set for body. (%s)"), *ObjectName); + return bRestoredState; + } + else if (FMath::Abs(NewQuatSizeSqr - 1.f) > UE_KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Quaternion (%f %f %f %f) with non-unit magnitude detected. (%s)"), + NewState.Quaternion.X, NewState.Quaternion.Y, NewState.Quaternion.Z, NewState.Quaternion.W, *ObjectName); + return bRestoredState; + } + + // Grab configuration variables from engine config or from CVars if overriding is turned on. + static const auto CVarNetPingExtrapolation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingExtrapolation")); + const float NetPingExtrapolation = CVarNetPingExtrapolation->GetFloat() >= 0.0f ? CVarNetPingExtrapolation->GetFloat() : ErrorCorrectionDefault.PingExtrapolation; + + static const auto CVarNetPingLimit = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingLimit")); + const float NetPingLimit = CVarNetPingLimit->GetFloat() > 0.0f ? CVarNetPingLimit->GetFloat() : ErrorCorrectionDefault.PingLimit; + + static const auto CVarErrorPerLinearDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerLinearDifference")); + const float ErrorPerLinearDiff = CVarErrorPerLinearDifference->GetFloat() >= 0.0f ? CVarErrorPerLinearDifference->GetFloat() : ErrorCorrectionDefault.ErrorPerLinearDifference; + + static const auto CVarErrorPerAngularDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerAngularDifference")); + const float ErrorPerAngularDiff = CVarErrorPerAngularDifference->GetFloat() >= 0.0f ? CVarErrorPerAngularDifference->GetFloat() : ErrorCorrectionDefault.ErrorPerAngularDifference; + + static const auto CVarMaxRestoredStateError = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxRestoredStateError")); + const float MaxRestoredStateError = CVarMaxRestoredStateError->GetFloat() >= 0.0f ? CVarMaxRestoredStateError->GetFloat() : ErrorCorrectionDefault.MaxRestoredStateError; + + static const auto CVarErrorAccumulation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSeconds")); + const float ErrorAccumulationSeconds = CVarErrorAccumulation->GetFloat() >= 0.0f ? CVarErrorAccumulation->GetFloat() : ErrorCorrectionDefault.ErrorAccumulationSeconds; + + static const auto CVarErrorAccumulationDistanceSq = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationDistanceSq")); + const float ErrorAccumulationDistanceSq = CVarErrorAccumulationDistanceSq->GetFloat() >= 0.0f ? CVarErrorAccumulationDistanceSq->GetFloat() : ErrorCorrectionDefault.ErrorAccumulationDistanceSq; + + static const auto CVarErrorAccumulationSimilarity = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSimilarity")); + const float ErrorAccumulationSimilarity = CVarErrorAccumulationSimilarity->GetFloat() >= 0.0f ? CVarErrorAccumulationSimilarity->GetFloat() : ErrorCorrectionDefault.ErrorAccumulationSimilarity; + + static const auto CVarLinSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PositionLerp")); + const float PositionLerp = CVarLinSet->GetFloat() >= 0.0f ? CVarLinSet->GetFloat() : ErrorCorrectionDefault.PositionLerp; + + static const auto CVarLinLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.LinearVelocityCoefficient")); + const float LinearVelocityCoefficient = CVarLinLerp->GetFloat() >= 0.0f ? CVarLinLerp->GetFloat() : ErrorCorrectionDefault.LinearVelocityCoefficient; + + static const auto CVarAngSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngleLerp")); + const float AngleLerp = CVarAngSet->GetFloat() >= 0.0f ? CVarAngSet->GetFloat() : ErrorCorrectionDefault.AngleLerp; + + static const auto CVarAngLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngularVelocityCoefficient")); + const float AngularVelocityCoefficient = CVarAngLerp->GetFloat() >= 0.0f ? CVarAngLerp->GetFloat() : ErrorCorrectionDefault.AngularVelocityCoefficient; + + static const auto CVarMaxLinearHardSnapDistance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxLinearHardSnapDistance")); + const float MaxLinearHardSnapDistance = CVarMaxLinearHardSnapDistance->GetFloat() >= 0.f ? CVarMaxLinearHardSnapDistance->GetFloat() : ErrorCorrectionDefault.MaxLinearHardSnapDistance; + + // Get Current state + FRigidBodyState CurrentState; + CurrentState.Position = Handle->X(); + CurrentState.Quaternion = Handle->R(); + CurrentState.AngVel = Handle->W(); + CurrentState.LinVel = Handle->V(); + + + // Starting from the last known authoritative position, and + // extrapolate an approximation using the last known velocity + // and ping. + const float PingSeconds = FMath::Clamp(LatencyOneWay, 0.f, NetPingLimit); + const float ExtrapolationDeltaSeconds = PingSeconds * NetPingExtrapolation; + const FVector ExtrapolationDeltaPos = NewState.LinVel * ExtrapolationDeltaSeconds; + const FVector_NetQuantize100 TargetPos = NewState.Position + ExtrapolationDeltaPos; + float NewStateAngVel; + FVector NewStateAngVelAxis; + NewState.AngVel.FVector::ToDirectionAndLength(NewStateAngVelAxis, NewStateAngVel); + NewStateAngVel = FMath::DegreesToRadians(NewStateAngVel); + const FQuat ExtrapolationDeltaQuaternion = FQuat(NewStateAngVelAxis, NewStateAngVel * ExtrapolationDeltaSeconds); + FQuat TargetQuat = ExtrapolationDeltaQuaternion * NewState.Quaternion; + + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + float AngDiffSize; + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + /////// ACCUMULATE ERROR IF NOT APPROACHING SOLUTION /////// + + // Store sleeping state + const bool bShouldSleep = (NewState.Flags & ERigidBodyFlags::Sleeping) != 0; + const bool bWasAwake = !Handle->Sleeping(); + const bool bAutoWake = false; + + const float Error = (LinDiffSize * ErrorPerLinearDiff) + (AngDiffSize * ErrorPerAngularDiff); + + bRestoredState = Error < MaxRestoredStateError; + if (bRestoredState) + { + Target.AccumulatedErrorSeconds = 0.0f; + } + else + { + // + // The heuristic for error accumulation here is: + + // 1. Did the physics tick from the previous step fail to + // move the body towards a resolved position? + // 2. Was the linear error in the same direction as the + // previous frame? + // 3. Is the linear error large enough to accumulate error? + // + // If these conditions are met, then "error" time will accumulate. + // Once error has accumulated for a certain number of seconds, + // a hard-snap to the target will be performed. + // + // TODO: Rotation while moving linearly can still mess up this + // heuristic. We need to account for it. + // + + // Project the change in position from the previous tick onto the + // linear error from the previous tick. This value roughly represents + // how much correction was performed over the previous physics tick. + const float PrevProgress = FVector::DotProduct( + FVector(CurrentState.Position) - Target.PrevPos, + (Target.PrevPosTarget - Target.PrevPos).GetSafeNormal()); + + // Project the current linear error onto the linear error from the + // previous tick. This value roughly represents how little the direction + // of the linear error state has changed, and how big the error is. + const float PrevSimilarity = FVector::DotProduct( + TargetPos - FVector(CurrentState.Position), + Target.PrevPosTarget - Target.PrevPos); + + // If the conditions from the heuristic outlined above are met, accumulate + // error. Otherwise, reduce it. + if (PrevProgress < ErrorAccumulationDistanceSq && + PrevSimilarity > ErrorAccumulationSimilarity) + { + Target.AccumulatedErrorSeconds += DeltaSeconds; + } + else + { + Target.AccumulatedErrorSeconds = FMath::Max(Target.AccumulatedErrorSeconds - DeltaSeconds, 0.0f); + } + + // Hard snap if error accumulation or linear error is big enough, and clear the error accumulator. + const bool bHardSnap = + LinDiffSize > MaxLinearHardSnapDistance || + Target.AccumulatedErrorSeconds > ErrorAccumulationSeconds || + CharacterMovementCVars::AlwaysHardSnap; + + if (bHardSnap) + { +#if !UE_BUILD_SHIPPING + if (PhysicsReplicationCVars::LogPhysicsReplicationHardSnaps) + { + UE_LOG(LogTemp, Warning, TEXT("Simulated HARD SNAP - \nCurrent Pos - %s, Target Pos - %s\n CurrentState.LinVel - %s, New Lin Vel - %s\nTarget Extrapolation Delta - %s, Is Asleep - %d, Prev Progress - %f, Prev Similarity - %f"), + *CurrentState.Position.ToString(), *TargetPos.ToString(), *CurrentState.LinVel.ToString(), *NewState.LinVel.ToString(), + *ExtrapolationDeltaPos.ToString(), Handle->Sleeping(), PrevProgress, PrevSimilarity); + + if (LinDiffSize > MaxLinearHardSnapDistance) + { + UE_LOG(LogTemp, Warning, TEXT("Hard snap due to linear difference error")); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Hard snap due to accumulated error")) + } + } +#endif + // Too much error so just snap state here and be done with it + Target.AccumulatedErrorSeconds = 0.0f; + bRestoredState = true; + Handle->SetX(TargetPos); + Handle->SetR(TargetQuat); + Handle->SetV(NewState.LinVel); + Handle->SetW(FMath::DegreesToRadians(NewState.AngVel)); + } + else + { + const FVector NewLinVel = FVector(Target.TargetState.LinVel) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(Target.TargetState.AngVel) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), TargetPos, PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + Handle->SetX(NewPos); + Handle->SetR(NewAng); + Handle->SetV(NewLinVel); + Handle->SetW(FMath::DegreesToRadians(NewAngVel)); + } + } + + if (bShouldSleep) + { + // don't allow kinematic to sleeping transition + if (Handle->ObjectState() != Chaos::EObjectStateType::Kinematic) + { + RigidsSolver->GetEvolution()->SetParticleObjectState(Handle, Chaos::EObjectStateType::Sleeping); + } + } + + Target.PrevPosTarget = TargetPos; + Target.PrevPos = FVector(CurrentState.Position); + + return bRestoredState; +} + +/** Interpolating towards replicated states from the server while predicting local physics +* TODO, detailed description +*/ +bool FPhysicsReplicationAsyncVR::PredictiveInterpolation(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds) +{ + if (Target.bWaiting) + { + return false; + } + + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + if (RigidsSolver == nullptr) + { + return true; + } + + static const auto CVarErrorAccumulationSeconds = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSeconds")); + const float ErrorAccumulationSeconds = CVarErrorAccumulationSeconds->GetFloat() >= 0.0f ? CVarErrorAccumulationSeconds->GetFloat() : ErrorCorrectionDefault.ErrorAccumulationSeconds; + + static const auto CVarMaxRestoredStateError = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxRestoredStateError")); + const float MaxRestoredStateErrorSqr = CVarMaxRestoredStateError->GetFloat() >= 0.0f ? + (CVarMaxRestoredStateError->GetFloat() * CVarMaxRestoredStateError->GetFloat()) : + (ErrorCorrectionDefault.MaxRestoredStateError * ErrorCorrectionDefault.MaxRestoredStateError); + + const bool bShouldSleep = (Target.TargetState.Flags & ERigidBodyFlags::Sleeping) != 0; + const int32 LocalFrame = Target.ServerFrame - Target.FrameOffset; + const int32 NumPredictedFrames = RigidsSolver->GetCurrentFrame() - LocalFrame - Target.TickCount; + const float PredictedTime = DeltaSeconds * NumPredictedFrames; + const float SendRate = (Target.ServerFrame - Target.PrevServerFrame) * DeltaSeconds; + + static const auto CVarPosCorrectionTimeMultiplier = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.PosCorrectionTimeMultiplier")); + const float PosCorrectionTime = PredictedTime * CVarPosCorrectionTimeMultiplier->GetFloat(); + + static const auto CVarInterpolationTimeMultiplier = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.InterpolationTimeMultiplier")); + const float InterpolationTime = SendRate * CVarInterpolationTimeMultiplier->GetFloat(); + + // CurrentState + FRigidBodyState CurrentState; + CurrentState.Position = Handle->X(); + CurrentState.Quaternion = Handle->R(); + CurrentState.LinVel = Handle->V(); + CurrentState.AngVel = Handle->W(); + + // NewState + const FVector TargetPos = Target.TargetState.Position; + const FQuat TargetRot = Target.TargetState.Quaternion; + const FVector TargetLinVel = Target.TargetState.LinVel; + const FVector TargetAngVel = Target.TargetState.AngVel; + + + /** --- Reconciliation --- + * Get the traveled direction and distance from previous frame and compare with replicated linear velocity. + * If the object isn't moving enough along the replicated velocity it's considered stuck and needs a hard reconciliation. + */ + const FVector PrevDiff = CurrentState.Position - Target.PrevPos; + const float ExpectedDistance = (Target.PrevLinVel * DeltaSeconds).Size(); + const float CoveredDistance = FVector::DotProduct(PrevDiff, Target.PrevLinVel.GetSafeNormal()); + + // If the object is moving less than X% of the expected distance, accumulate error seconds + static const auto CVarMinExpectedDistanceCovered = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.MinExpectedDistanceCovered")); + if (CoveredDistance / ExpectedDistance < CVarMinExpectedDistanceCovered->GetFloat()) + { + Target.AccumulatedErrorSeconds += DeltaSeconds; + } + else + { + Target.AccumulatedErrorSeconds = FMath::Max(Target.AccumulatedErrorSeconds - DeltaSeconds, 0.0f); + } + + static const auto CVarbPredictiveInterpolationAlwaysHardSnap = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PredictiveInterpolation.AlwaysHardSnap")); + const bool bHardSnap = Target.AccumulatedErrorSeconds > ErrorAccumulationSeconds || CVarbPredictiveInterpolationAlwaysHardSnap->GetBool(); + bool bClearTarget = bHardSnap; + if (bHardSnap) + { + // Too much error so just snap state here and be done with it + Target.AccumulatedErrorSeconds = 0.0f; + Handle->SetX(Target.PrevPosTarget); + Handle->SetP(Target.PrevPosTarget); + Handle->SetR(Target.PrevRotTarget); + Handle->SetQ(Target.PrevRotTarget); + Handle->SetV(Target.TargetState.LinVel); + Handle->SetW(Target.TargetState.AngVel); + } + else // Velocity-based Replication + { + const Chaos::EObjectStateType ObjectState = Handle->ObjectState(); + if (ObjectState != Chaos::EObjectStateType::Dynamic) + { + RigidsSolver->GetEvolution()->SetParticleObjectState(Handle, Chaos::EObjectStateType::Dynamic); + } + + + // --- Velocity Replication --- + // Get PosDiff + const FVector PosDiff = TargetPos - CurrentState.Position; + + // Convert PosDiff to a velocity + const FVector PosDiffVelocity = PosDiff / PosCorrectionTime; + + // Get LinVelDiff by adding inverted CurrentState.LinVel to TargetLinVel + const FVector LinVelDiff = -CurrentState.LinVel + TargetLinVel; + + // Add PosDiffVelocity to LinVelDiff to get BlendedTargetVelocity + const FVector BlendedTargetVelocity = LinVelDiff + PosDiffVelocity; + + // Multiply BlendedTargetVelocity with(deltaTime / interpolationTime), clamp to 1 and add to CurrentState.LinVel to get BlendedTargetVelocityInterpolated + const float BlendStepAmount = FMath::Clamp(DeltaSeconds / InterpolationTime, 0.f, 1.f); + const FVector RepLinVel = CurrentState.LinVel + (BlendedTargetVelocity * BlendStepAmount); + + + // --- Angular Velocity Replication --- + // Extrapolate current rotation along current angular velocity to see where we would end up + float CurAngVelSize; + FVector CurAngVelAxis; + CurrentState.AngVel.FVector::ToDirectionAndLength(CurAngVelAxis, CurAngVelSize); + CurAngVelSize = FMath::DegreesToRadians(CurAngVelSize); + const FQuat CurRotExtrapDelta = FQuat(CurAngVelAxis, CurAngVelSize * DeltaSeconds); + const FQuat CurRotExtrap = CurRotExtrapDelta * CurrentState.Quaternion; + + // Slerp from the extrapolated current rotation towards the target rotation + // This takes current angular velocity into account + const FQuat TargetRotBlended = FQuat::Slerp(CurRotExtrap, TargetRot, BlendStepAmount); + + // Get the rotational offset between the blended rotation target and the current rotation + const FQuat TargetRotDelta = TargetRotBlended * CurrentState.Quaternion.Inverse(); + + // Convert the rotational delta to angular velocity with a magnitude that will make it arrive at the rotation after DeltaTime has passed + float WAngle; + FVector WAxis; + TargetRotDelta.ToAxisAndAngle(WAxis, WAngle); + + const FVector RepAngVel = WAxis * (WAngle / DeltaSeconds); + + + // Apply velocity + Handle->SetV(RepLinVel); + Handle->SetW(RepAngVel); + + + // Cache data for reconciliation + Target.PrevPos = FVector(CurrentState.Position); + Target.PrevLinVel = FVector(RepLinVel); + } + + + if (bShouldSleep) + { + // --- Sleep --- + // Get the distance from the current position to the source position of our target state + const float SourceDistanceSqr = (Target.PrevPosTarget - CurrentState.Position).SizeSquared(); + + // Don't allow kinematic to sleeping transition + static const auto CVarMaxDistanceToSleepSqr = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.MaxDistanceToSleepSqr")); + if (SourceDistanceSqr < CVarMaxDistanceToSleepSqr->GetFloat() && !Handle->IsKinematic()) + { + RigidsSolver->GetEvolution()->SetParticleObjectState(Handle, Chaos::EObjectStateType::Sleeping); + bClearTarget = true; + } + } + else + { + // --- Target Extrapolation --- + static const auto CVarExtrapolationTimeMultiplier = IConsoleManager::Get().FindConsoleVariable(TEXT("np2.PredictiveInterpolation.ExtrapolationTimeMultiplier")); + if ((Target.TickCount * DeltaSeconds) < SendRate * CVarExtrapolationTimeMultiplier->GetFloat()) + { + // Extrapolate target position + Target.TargetState.Position = Target.TargetState.Position + Target.TargetState.LinVel * DeltaSeconds; + + // Extrapolate target rotation + float TargetAngVelSize; + FVector TargetAngVelAxis; + Target.TargetState.AngVel.FVector::ToDirectionAndLength(TargetAngVelAxis, TargetAngVelSize); + TargetAngVelSize = FMath::DegreesToRadians(TargetAngVelSize); + const FQuat TargetRotExtrapDelta = FQuat(TargetAngVelAxis, TargetAngVelSize * DeltaSeconds); + Target.TargetState.Quaternion = TargetRotExtrapDelta * Target.TargetState.Quaternion; + } + } + + Target.TickCount++; + + return bClearTarget;; +} + +/** Compare states and trigger resimulation if needed */ +bool FPhysicsReplicationAsyncVR::ResimulationReplication(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds) +{ + Chaos::FPBDRigidsSolver* RigidsSolver = static_cast(GetSolver()); + if (RigidsSolver == nullptr) + { + return true; + } + + Chaos::FRewindData* RewindData = RigidsSolver->GetRewindData(); + if (RewindData == nullptr) + { + return true; + } + + const int32 LocalFrame = Target.ServerFrame - Target.FrameOffset; + + if (LocalFrame <= RewindData->CurrentFrame() && LocalFrame >= RewindData->GetEarliestFrame_Internal()) + { + static constexpr Chaos::FFrameAndPhase::EParticleHistoryPhase RewindPhase = Chaos::FFrameAndPhase::EParticleHistoryPhase::PostPushData; + + FAsyncPhysicsTimestamp TimeStamp; + TimeStamp.LocalFrame = RewindData->CurrentFrame(); + + const float ResimErrorThreshold = Chaos::FPhysicsSolverBase::ResimulationErrorThreshold(); + + auto PastState = RewindData->GetPastStateAtFrame(*Handle, LocalFrame, RewindPhase); + + const FVector ErrorOffset = (PastState.X() - Target.TargetState.Position); + const float ErrorDistance = ErrorOffset.Size(); + const bool ShouldTriggerResim = ErrorDistance >= ResimErrorThreshold; + float ColorLerp = ShouldTriggerResim ? 1.0f : 0.0f; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + if (Chaos::FPhysicsSolverBase::CanDebugNetworkPhysicsPrediction()) + { + UE_LOG(LogTemp, Log, TEXT("Apply Rigid body state at local frame %d with offset = %d"), LocalFrame, Target.FrameOffset); + UE_LOG(LogTemp, Log, TEXT("Particle Position Error = %f | Should Trigger Resim = %s | Server Frame = %d | Client Frame = %d"), ErrorDistance, (ShouldTriggerResim ? TEXT("True") : TEXT("False")), Target.ServerFrame, LocalFrame); + UE_LOG(LogTemp, Log, TEXT("Particle Target Position = %s | Current Position = %s"), *Target.TargetState.Position.ToString(), *PastState.X().ToString()); + UE_LOG(LogTemp, Log, TEXT("Particle Target Velocity = %s | Current Velocity = %s"), *Target.TargetState.LinVel.ToString(), *PastState.V().ToString()); + UE_LOG(LogTemp, Log, TEXT("Particle Target Quaternion = %s | Current Quaternion = %s"), *Target.TargetState.Quaternion.ToString(), *PastState.R().ToString()); + UE_LOG(LogTemp, Log, TEXT("Particle Target Omega = %s | Current Omega= %s"), *Target.TargetState.AngVel.ToString(), *PastState.W().ToString()); + + { // DrawDebug + static constexpr float BoxSize = 5.0f; + const FColor DebugColor = FLinearColor::LerpUsingHSV(FLinearColor::Green, FLinearColor::Red, ColorLerp).ToFColor(false); + + static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime")); + + Chaos::FDebugDrawQueue::GetInstance().DrawDebugBox(Target.TargetState.Position, FVector(BoxSize, BoxSize, BoxSize), Target.TargetState.Quaternion, FColor::Orange, true, CVarNetCorrectionLifetime->GetFloat(), 0, 1.0f); + Chaos::FDebugDrawQueue::GetInstance().DrawDebugBox(PastState.X(), FVector(6, 6, 6), PastState.R(), DebugColor, true, CVarNetCorrectionLifetime->GetFloat(), 0, 1.0f); + + Chaos::FDebugDrawQueue::GetInstance().DrawDebugDirectionalArrow(PastState.X(), Target.TargetState.Position, 5.0f, FColor::Green, true, CVarNetCorrectionLifetime->GetFloat(), 0, 0.5f); + } + } +#endif + + if (ShouldTriggerResim) + { + RigidsSolver->GetEvolution()->GetIslandManager().SetParticleResimFrame(Handle, LocalFrame); + + int32 ResimFrame = RewindData->GetResimFrame(); + ResimFrame = (ResimFrame == INDEX_NONE) ? LocalFrame : FMath::Min(ResimFrame, LocalFrame); + RewindData->SetResimFrame(ResimFrame); + } + } + else if (LocalFrame > 0) + { + UE_LOG(LogPhysics, Warning, TEXT("FPhysicsReplication::ApplyRigidBodyState target frame (%d) out of rewind data bounds (%d,%d)"), LocalFrame, + RewindData->GetEarliestFrame_Internal(), RewindData->CurrentFrame()); + } + + return true; +} + +FName FPhysicsReplicationAsyncVR::GetFNameForStatId() const +{ + const static FLazyName StaticName("FPhysicsReplicationAsyncCallback"); + return StaticName; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp new file mode 100644 index 0000000..330ee78 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp @@ -0,0 +1,910 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSkeletalMeshActor.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableSkeletalMeshActor) + +#include "TimerManager.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "Physics/Experimental/PhysScene_Chaos.h" +#include "PhysicsEngine/PhysicsAsset.h" // Tmp until epic bug fixes skeletal welding +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +UOptionalRepSkeletalMeshComponent::UOptionalRepSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; +} + +void UOptionalRepSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UOptionalRepSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UOptionalRepSkeletalMeshComponent, bReplicateMovement); +} + +void UOptionalRepSkeletalMeshComponent::GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) +{ + UPhysicsAsset* PhysicsAsset = GetPhysicsAsset(); + + for (int32 BodyIdx = 0; BodyIdx < Bodies.Num(); ++BodyIdx) + { + FBodyInstance* BI = Bodies[BodyIdx]; + if (BI && (BI->WeldParent != nullptr || (bIncludingAutoWeld && BI->bAutoWeld))) + { + OutWeldedBodies.Add(BI); + if (PhysicsAsset) + { + if (UBodySetup* PhysicsAssetBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIdx]) + { + OutLabels.Add(PhysicsAssetBodySetup->BoneName); + } + else + { + OutLabels.Add(NAME_None); + } + } + else + { + OutLabels.Add(NAME_None); + } + + } + } + + for (USceneComponent* Child : GetAttachChildren()) + { + if (UPrimitiveComponent* PrimChild = Cast(Child)) + { + PrimChild->GetWeldedBodies(OutWeldedBodies, OutLabels, bIncludingAutoWeld); + } + } +} + +FBodyInstance* UOptionalRepSkeletalMeshComponent::GetBodyInstance(FName BoneName, bool bGetWelded, int32) const +{ + UPhysicsAsset* const PhysicsAsset = GetPhysicsAsset(); + FBodyInstance* BodyInst = NULL; + + if (PhysicsAsset != NULL) + { + // A name of NAME_None indicates 'root body' + if (BoneName == NAME_None) + { + if (Bodies.IsValidIndex(RootBodyData.BodyIndex)) + { + BodyInst = Bodies[RootBodyData.BodyIndex]; + } + } + // otherwise, look for the body + else + { + int32 BodyIndex = PhysicsAsset->FindBodyIndex(BoneName); + if (Bodies.IsValidIndex(BodyIndex)) + { + BodyInst = Bodies[BodyIndex]; + } + } + + BodyInst = (bGetWelded && BodyInstance.WeldParent) ? BodyInstance.WeldParent : BodyInst; + } + + return BodyInst; +} + +//============================================================================= +AGrippableSkeletalMeshActor::AGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("SkeletalMeshComponent0"))) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; + + bAllowIgnoringAttachOnOwner = true; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableSkeletalMeshActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableSkeletalMeshActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableSkeletalMeshActor::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableSkeletalMeshActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableSkeletalMeshActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableSkeletalMeshActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableSkeletalMeshActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableSkeletalMeshActor, AttachmentWeldReplication, this); + } +#endif + + /*PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS*/ +} + +void AGrippableSkeletalMeshActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { +#if UE_WITH_IRIS + const bool bPrevRepPhysics = GetReplicatedMovement_Mutable().bRepPhysics; +#endif // UE_WITH_IRIS + + bool bFoundInCache = false; + + UWorld* World = GetWorld(); + int ServerFrame = 0; + if (FPhysScene_Chaos* Scene = static_cast(World->GetPhysicsScene())) + { + if (const FRigidBodyState* FoundState = Scene->GetStateFromReplicationCache(RootPrimComp, ServerFrame)) + { + RepMovement.FillFrom(*FoundState, this, Scene->ReplicationCache.ServerFrame); + bFoundInCache = true; + } + } + + if (!bFoundInCache) + { + // fallback to GT data + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + RepMovement.FillFrom(RBState, this, 0); + } + + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + +#if UE_WITH_IRIS + // If RepPhysics has changed value then notify the ReplicationSystem + if (bPrevRepPhysics != GetReplicatedMovement_Mutable().bRepPhysics) + { + UpdateReplicatePhysicsCondition(); + } +#endif // UE_WITH_IRIS + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParentActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableSkeletalMeshActor, AttachmentWeldReplication, this); + } +#endif + } +} + +bool AGrippableSkeletalMeshActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch* Bunch, FReplicationFlags* RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +/*void AGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + DOREPLIFETIME(AGrippableSkeletalMeshActor, VRGripInterfaceSettings); +}*/ + +//============================================================================= +AGrippableSkeletalMeshActor::~AGrippableSkeletalMeshActor() +{ +} + +void AGrippableSkeletalMeshActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + +void AGrippableSkeletalMeshActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableSkeletalMeshActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableSkeletalMeshActor::TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) {} +void AGrippableSkeletalMeshActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void AGrippableSkeletalMeshActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableSkeletalMeshActor::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableSkeletalMeshActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void AGrippableSkeletalMeshActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableSkeletalMeshActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableSkeletalMeshActor::OnUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnEndUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnSecondaryUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableSkeletalMeshActor::RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) { return false; } + +bool AGrippableSkeletalMeshActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableSkeletalMeshActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableSkeletalMeshActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableSkeletalMeshActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableSkeletalMeshActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableSkeletalMeshActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableSkeletalMeshActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableSkeletalMeshActor::GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableSkeletalMeshActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableSkeletalMeshActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableSkeletalMeshActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableSkeletalMeshActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableSkeletalMeshActor::IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableSkeletalMeshActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld* World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableSkeletalMeshActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableSkeletalMeshActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableSkeletalMeshActor::SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +/*FBPInteractionSettings AGrippableSkeletalMeshActor::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool AGrippableSkeletalMeshActor::GetGripScripts_Implementation(TArray& ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +bool AGrippableSkeletalMeshActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld* OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent* PrimComp = Cast(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor* tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController* PlayerController = Cast(TopOwner)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableSkeletalMeshActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableSkeletalMeshActor::CeaseReplicationBlocking() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld* OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableSkeletalMeshActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableSkeletalMeshActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableSkeletalMeshActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + PhysicsReplication->RemoveReplicatedTarget(this->GetSkeletalMeshComponent()); + } + } + } +} + +bool AGrippableSkeletalMeshActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR& newMovement) +{ + return true; +} + +void AGrippableSkeletalMeshActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR& newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableSkeletalMeshActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableSkeletalMeshActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableSkeletalMeshActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableSkeletalMeshActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} + +void AGrippableSkeletalMeshActor::MarkComponentsAsGarbage(bool bModify) +{ + Super::MarkComponentsAsGarbage(bModify); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent* ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp new file mode 100644 index 0000000..6e9dfad --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp @@ -0,0 +1,392 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSkeletalMeshComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableSkeletalMeshComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "PhysicsEngine/PhysicsAsset.h" // Tmp until epic bug fixes skeletal welding +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableSkeletalMeshComponent::UGrippableSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; +} + +void UGrippableSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, GameplayTags, COND_Custom); +} + +void UGrippableSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSkeletalMeshComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSkeletalMeshComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSkeletalMeshComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableSkeletalMeshComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableSkeletalMeshComponent::~UGrippableSkeletalMeshComponent() +{ +} + +void UGrippableSkeletalMeshComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableSkeletalMeshComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableSkeletalMeshComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableSkeletalMeshComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableSkeletalMeshComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableSkeletalMeshComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableSkeletalMeshComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableSkeletalMeshComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSkeletalMeshComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableSkeletalMeshComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableSkeletalMeshComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableSkeletalMeshComponent::OnUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnEndUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnSecondaryUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableSkeletalMeshComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableSkeletalMeshComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableSkeletalMeshComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableSkeletalMeshComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableSkeletalMeshComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableSkeletalMeshComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableSkeletalMeshComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableSkeletalMeshComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableSkeletalMeshComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableSkeletalMeshComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableSkeletalMeshComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableSkeletalMeshComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableSkeletalMeshComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableSkeletalMeshComponent::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableSkeletalMeshComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableSkeletalMeshComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableSkeletalMeshComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableSkeletalMeshComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableSkeletalMeshComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableSkeletalMeshComponent::GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableSkeletalMeshComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableSkeletalMeshComponent::GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) +{ + UPhysicsAsset* PhysicsAsset = GetPhysicsAsset(); + + for (int32 BodyIdx = 0; BodyIdx < Bodies.Num(); ++BodyIdx) + { + FBodyInstance* BI = Bodies[BodyIdx]; + if (BI && (BI->WeldParent != nullptr || (bIncludingAutoWeld && BI->bAutoWeld))) + { + OutWeldedBodies.Add(BI); + if (PhysicsAsset) + { + if (UBodySetup* PhysicsAssetBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIdx]) + { + OutLabels.Add(PhysicsAssetBodySetup->BoneName); + } + else + { + OutLabels.Add(NAME_None); + } + } + else + { + OutLabels.Add(NAME_None); + } + + } + } + + for (USceneComponent* Child : GetAttachChildren()) + { + if (UPrimitiveComponent* PrimChild = Cast(Child)) + { + PrimChild->GetWeldedBodies(OutWeldedBodies, OutLabels, bIncludingAutoWeld); + } + } +} + +FBodyInstance* UGrippableSkeletalMeshComponent::GetBodyInstance(FName BoneName, bool bGetWelded, int32) const +{ + UPhysicsAsset* const PhysicsAsset = GetPhysicsAsset(); + FBodyInstance* BodyInst = NULL; + + if (PhysicsAsset != NULL) + { + // A name of NAME_None indicates 'root body' + if (BoneName == NAME_None) + { + if (Bodies.IsValidIndex(RootBodyData.BodyIndex)) + { + BodyInst = Bodies[RootBodyData.BodyIndex]; + } + } + // otherwise, look for the body + else + { + int32 BodyIndex = PhysicsAsset->FindBodyIndex(BoneName); + if (Bodies.IsValidIndex(BodyIndex)) + { + BodyInst = Bodies[BodyIndex]; + } + } + + BodyInst = (bGetWelded && BodyInstance.WeldParent) ? BodyInstance.WeldParent : BodyInst; + } + + return BodyInst; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp new file mode 100644 index 0000000..3cebb3c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp @@ -0,0 +1,322 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSphereComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableSphereComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableSphereComponent::UGrippableSphereComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + bReplicateMovement = false; + //this->bReplicates = true; + + VRGripInterfaceSettings.bIsHeld = false; + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; +} + +void UGrippableSphereComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableSphereComponent, bReplicateGripScripts) + DOREPLIFETIME(UGrippableSphereComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableSphereComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, GameplayTags, COND_Custom); +} + +void UGrippableSphereComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSphereComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSphereComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableSphereComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableSphereComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableSphereComponent::~UGrippableSphereComponent() +{ +} + +void UGrippableSphereComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableSphereComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableSphereComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableSphereComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableSphereComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableSphereComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSphereComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableSphereComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSphereComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableSphereComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableSphereComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableSphereComponent::OnUsed_Implementation() {} +void UGrippableSphereComponent::OnEndUsed_Implementation() {} +void UGrippableSphereComponent::OnSecondaryUsed_Implementation() {} +void UGrippableSphereComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableSphereComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableSphereComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableSphereComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableSphereComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableSphereComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableSphereComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableSphereComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableSphereComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableSphereComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableSphereComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableSphereComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableSphereComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableSphereComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableSphereComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableSphereComponent::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableSphereComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableSphereComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableSphereComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + + +bool UGrippableSphereComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableSphereComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableSphereComponent::GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableSphereComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp new file mode 100644 index 0000000..623d7d7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp @@ -0,0 +1,861 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableStaticMeshActor.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableStaticMeshActor) + +#include "TimerManager.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "GripScripts/VRGripScriptBase.h" +#include "DrawDebugHelpers.h" +#include "Physics/Experimental/PhysScene_Chaos.h" + +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +// #TODO: Pull request this? This macro could be very useful +/*#define DOREPLIFETIME_CHANGE_NOTIFY(c,v,rncond) \ +{ \ + static UProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \ + bool bFound = false; \ + for ( int32 i = 0; i < OutLifetimeProps.Num(); i++ ) \ + { \ + if ( OutLifetimeProps[i].RepIndex == sp##v->RepIndex ) \ + { \ + for ( int32 j = 0; j < sp##v->ArrayDim; j++ ) \ + { \ + OutLifetimeProps[i + j].RepNotifyCondition = rncond; \ + } \ + bFound = true; \ + break; \ + } \ + } \ + check( bFound ); \ +}*/ + + +UOptionalRepStaticMeshComponent::UOptionalRepStaticMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; +} + +void UOptionalRepStaticMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); + + +} + +void UOptionalRepStaticMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UOptionalRepStaticMeshComponent, bReplicateMovement); +} + + //============================================================================= +AGrippableStaticMeshActor::AGrippableStaticMeshActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("StaticMeshComponent0"))) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + this->SetMobility(EComponentMobility::Movable); + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; + + bAllowIgnoringAttachOnOwner = true; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableStaticMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableStaticMeshActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableStaticMeshActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableStaticMeshActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableStaticMeshActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableStaticMeshActor::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + //Super::PreReplication(ChangedPropertyTracker); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this); + } +#endif + + /*PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS*/ + +} + +void AGrippableStaticMeshActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { +#if UE_WITH_IRIS + const bool bPrevRepPhysics = GetReplicatedMovement_Mutable().bRepPhysics; +#endif // UE_WITH_IRIS + + bool bFoundInCache = false; + + UWorld* World = GetWorld(); + int ServerFrame = 0; + if (FPhysScene_Chaos* Scene = static_cast(World->GetPhysicsScene())) + { + if (const FRigidBodyState* FoundState = Scene->GetStateFromReplicationCache(RootPrimComp, ServerFrame)) + { + RepMovement.FillFrom(*FoundState, this, Scene->ReplicationCache.ServerFrame); + bFoundInCache = true; + } + } + + if (!bFoundInCache) + { + // fallback to GT data + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + RepMovement.FillFrom(RBState, this, 0); + } + + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + +#if UE_WITH_IRIS + // If RepPhysics has changed value then notify the ReplicationSystem + if (bPrevRepPhysics != GetReplicatedMovement_Mutable().bRepPhysics) + { + UpdateReplicatePhysicsCondition(); + } +#endif // UE_WITH_IRIS + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParentActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this); + } +#endif + } +} + +bool AGrippableStaticMeshActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +AGrippableStaticMeshActor::~AGrippableStaticMeshActor() +{ +} + +void AGrippableStaticMeshActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + +void AGrippableStaticMeshActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableStaticMeshActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableStaticMeshActor::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void AGrippableStaticMeshActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void AGrippableStaticMeshActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableStaticMeshActor::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableStaticMeshActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void AGrippableStaticMeshActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableStaticMeshActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableStaticMeshActor::OnUsed_Implementation() {} +void AGrippableStaticMeshActor::OnEndUsed_Implementation() {} +void AGrippableStaticMeshActor::OnSecondaryUsed_Implementation() {} +void AGrippableStaticMeshActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableStaticMeshActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableStaticMeshActor::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool AGrippableStaticMeshActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableStaticMeshActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableStaticMeshActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableStaticMeshActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableStaticMeshActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableStaticMeshActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableStaticMeshActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableStaticMeshActor::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableStaticMeshActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableStaticMeshActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableStaticMeshActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableStaticMeshActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableStaticMeshActor::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableStaticMeshActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld * World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableStaticMeshActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableStaticMeshActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableStaticMeshActor::SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +bool AGrippableStaticMeshActor::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +bool AGrippableStaticMeshActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld *OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent * PrimComp = Cast(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor * tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController* PlayerController = Cast(TopOwner)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableStaticMeshActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableStaticMeshActor::CeaseReplicationBlocking() +{ + if(ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld * OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableStaticMeshActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableStaticMeshActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableStaticMeshActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + PhysicsReplication->RemoveReplicatedTarget(this->GetStaticMeshComponent()); + } + } + } +} + +bool AGrippableStaticMeshActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR & newMovement) +{ + return true; +} + +void AGrippableStaticMeshActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR & newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableStaticMeshActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableStaticMeshActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableStaticMeshActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableStaticMeshActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} + +void AGrippableStaticMeshActor::MarkComponentsAsGarbage(bool bModify) +{ + Super::MarkComponentsAsGarbage(bModify); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent * ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + // We only call this on our interfaced components since they are the only ones that should implement grip scripts + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp new file mode 100644 index 0000000..dafe174 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp @@ -0,0 +1,317 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableStaticMeshComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableStaticMeshComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableStaticMeshComponent::UGrippableStaticMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + + // #TODO we can register them maybe in the future + // Don't use the replicated list, use our custom replication instead + bReplicateUsingRegisteredSubObjectList = false; +} + +void UGrippableStaticMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableStaticMeshComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableStaticMeshComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableStaticMeshComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, GameplayTags, COND_Custom); +} + +void UGrippableStaticMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableStaticMeshComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableStaticMeshComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UGrippableStaticMeshComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableStaticMeshComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableStaticMeshComponent::~UGrippableStaticMeshComponent() +{ +} + +void UGrippableStaticMeshComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableStaticMeshComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableStaticMeshComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableStaticMeshComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableStaticMeshComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableStaticMeshComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableStaticMeshComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableStaticMeshComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableStaticMeshComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableStaticMeshComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableStaticMeshComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableStaticMeshComponent::OnUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnEndUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnSecondaryUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableStaticMeshComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableStaticMeshComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableStaticMeshComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableStaticMeshComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableStaticMeshComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableStaticMeshComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableStaticMeshComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableStaticMeshComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableStaticMeshComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableStaticMeshComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableStaticMeshComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableStaticMeshComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableStaticMeshComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableStaticMeshComponent::IsHeld_Implementation(TArray & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableStaticMeshComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableStaticMeshComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +bool UGrippableStaticMeshComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableStaticMeshComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableStaticMeshComponent::GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableStaticMeshComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp new file mode 100644 index 0000000..5ef13c0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp @@ -0,0 +1,857 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/HandSocketComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(HandSocketComponent) + +#include "Engine/CollisionProfile.h" +#include "Animation/AnimSequence.h" +#include "Animation/AnimInstanceProxy.h" +#include "Animation/PoseSnapshot.h" +#include "Animation/AnimData/AnimDataModel.h" +//#include "VRExpansionFunctionLibrary.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/PoseableMeshComponent.h" +#include "GripMotionControllerComponent.h" +//#include "VRGripInterface.h" +//#include "VRBPDatatypes.h" +#include "Net/UnrealNetwork.h" +#include "Serialization/CustomVersion.h" + +DEFINE_LOG_CATEGORY(LogVRHandSocketComponent); + +const FGuid FVRHandSocketCustomVersion::GUID(0x5A018B7F, 0x48A7AFDE, 0xAFBEB580, 0xAD575412); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHandSocketCustomVersion(FVRHandSocketCustomVersion::GUID, FVRHandSocketCustomVersion::LatestVersion, TEXT("HandSocketVer")); + + +void UHandSocketComponent::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FVRHandSocketCustomVersion::GUID); + +#if WITH_EDITORONLY_DATA + const int32 CustomHandSocketVersion = Ar.CustomVer(FVRHandSocketCustomVersion::GUID); + + if (CustomHandSocketVersion < FVRHandSocketCustomVersion::HandSocketStoringSetState) + { + bDecoupled = bDecoupleMeshPlacement; + } +#endif +} + + //============================================================================= +UHandSocketComponent::UHandSocketComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = false; + PrimaryComponentTick.bCanEverTick = false; + PrimaryComponentTick.bStartWithTickEnabled = false; + // Setting absolute scale so we don't have to care about our parents scale + this->SetUsingAbsoluteScale(true); + //this->bReplicates = true; + + bRepGameplayTags = true; + +#if WITH_EDITORONLY_DATA + bTickedPose = false; + bDecoupled = false; + bShowVisualizationMesh = true; + bMirrorVisualizationMesh = false; + bShowRangeVisualization = false; + bFilterBonesByPostfix = false; + FilterPostfix = FString(TEXT("_r")); +#endif + + HandRelativePlacement = FTransform::Identity; + bAlwaysInRange = false; + bDisabled = false; + bMatchRotation = false; + OverrideDistance = 0.0f; + SlotPrefix = FName("VRGripP"); + bUseCustomPoseDeltas = false; + HandTargetAnimation = nullptr; + MirroredScale = FVector(1.f, 1.f, -1.f); + bOnlySnapMesh = false; + bOnlyUseHandPose = false; + bIgnoreAttachBone = false; + bFlipForLeftHand = false; + bLeftHandDominant = false; + bOnlyFlipRotation = false; + + MirrorAxis = EVRAxis::X; + FlipAxis = EVRAxis::Y; +} + +UAnimSequence* UHandSocketComponent::GetTargetAnimation() +{ + return HandTargetAnimation; +} + +bool UHandSocketComponent::GetAnimationSequenceAsPoseSnapShot(UAnimSequence* InAnimationSequence, FPoseSnapshot& OutPoseSnapShot, USkeletalMeshComponent* TargetMesh, bool bSkipRootBone, bool bFlipHand) +{ + if (InAnimationSequence) + { + OutPoseSnapShot.SkeletalMeshName = /*TargetMesh ? TargetMesh->SkeletalMesh->GetFName(): */InAnimationSequence->GetSkeleton()->GetFName(); + OutPoseSnapShot.SnapshotName = InAnimationSequence->GetFName(); + OutPoseSnapShot.BoneNames.Empty(); + OutPoseSnapShot.LocalTransforms.Empty(); + + TArray AnimSeqNames; + + if (USkeleton* AnimationSkele = InAnimationSequence->GetSkeleton()) + { + // pre-size the array to avoid unnecessary reallocation + OutPoseSnapShot.BoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + for (int32 i = 0; i < AnimationSkele->GetReferenceSkeleton().GetNum(); i++) + { + OutPoseSnapShot.BoneNames[i] = AnimationSkele->GetReferenceSkeleton().GetBoneName(i); + if (bFlipHand) + { + FString bName = OutPoseSnapShot.BoneNames[i].ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + OutPoseSnapShot.BoneNames[i] = FName(bName); + } + } + } + else + { + return false; + } + + const FReferenceSkeleton& RefSkeleton = (TargetMesh) ? TargetMesh->GetSkinnedAsset()->GetRefSkeleton() : InAnimationSequence->GetSkeleton()->GetReferenceSkeleton(); + FTransform LocalTransform; + + const TArray& TrackMap = InAnimationSequence->GetCompressedTrackToSkeletonMapTable(); + int32 TrackIndex = INDEX_NONE; + + OutPoseSnapShot.LocalTransforms.Reserve(OutPoseSnapShot.BoneNames.Num()); + + for (int32 BoneNameIndex = 0; BoneNameIndex < OutPoseSnapShot.BoneNames.Num(); ++BoneNameIndex) + { + + const FName& BoneName = OutPoseSnapShot.BoneNames[BoneNameIndex]; + + TrackIndex = INDEX_NONE; + if (BoneNameIndex != INDEX_NONE && BoneNameIndex < TrackMap.Num() && TrackMap[BoneNameIndex].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = BoneNameIndex; + } + else + { + // This shouldn't happen but I need a fallback + // Don't currently want to reconstruct the map inversely + for (int i = 0; i < TrackMap.Num(); ++i) + { + if (TrackMap[i].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = i; + break; + } + } + } + + if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0)) + { + double TrackLocation = 0.0f; + InAnimationSequence->GetBoneTransform(LocalTransform, FSkeletonPoseBoneIndex(TrackMap[TrackIndex].BoneTreeIndex), TrackLocation, false); + } + else + { + // otherwise, get ref pose if exists + const int32 BoneIDX = RefSkeleton.FindBoneIndex(BoneName); + if (BoneIDX != INDEX_NONE) + { + LocalTransform = RefSkeleton.GetRefBonePose()[BoneIDX]; + } + else + { + LocalTransform = FTransform::Identity; + } + } + + if (bFlipHand && (!bSkipRootBone || TrackIndex != 0)) + { + FMatrix M = LocalTransform.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + LocalTransform.SetFromMatrix(M); + } + + OutPoseSnapShot.LocalTransforms.Add(LocalTransform); + } + + OutPoseSnapShot.bIsValid = true; + return true; + } + + return false; +} + +bool UHandSocketComponent::GetBlendedPoseSnapShot(FPoseSnapshot& PoseSnapShot, USkeletalMeshComponent* TargetMesh, bool bSkipRootBone, bool bFlipHand) +{ + if (HandTargetAnimation)// && bUseCustomPoseDeltas && CustomPoseDeltas.Num() > 0) + { + PoseSnapShot.SkeletalMeshName = HandTargetAnimation->GetSkeleton()->GetFName(); + PoseSnapShot.SnapshotName = HandTargetAnimation->GetFName(); + PoseSnapShot.BoneNames.Empty(); + PoseSnapShot.LocalTransforms.Empty(); + TArray OrigBoneNames; + + if(USkeleton * AnimationSkele = HandTargetAnimation->GetSkeleton()) + { + // pre-size the array to avoid unnecessary reallocation + PoseSnapShot.BoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + OrigBoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + for (int32 i = 0; i < AnimationSkele->GetReferenceSkeleton().GetNum(); i++) + { + PoseSnapShot.BoneNames[i] = AnimationSkele->GetReferenceSkeleton().GetBoneName(i); + OrigBoneNames[i] = PoseSnapShot.BoneNames[i]; + if (bFlipHand) + { + FString bName = PoseSnapShot.BoneNames[i].ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + PoseSnapShot.BoneNames[i] = FName(bName); + } + } + } + else + { + return false; + } + + const FReferenceSkeleton& RefSkeleton = (TargetMesh) ? TargetMesh->GetSkinnedAsset()->GetRefSkeleton() : HandTargetAnimation->GetSkeleton()->GetReferenceSkeleton(); + FTransform LocalTransform; + + const TArray& TrackMap = HandTargetAnimation->GetCompressedTrackToSkeletonMapTable(); + int32 TrackIndex = INDEX_NONE; + + for (int32 BoneNameIndex = 0; BoneNameIndex < PoseSnapShot.BoneNames.Num(); ++BoneNameIndex) + { + TrackIndex = INDEX_NONE; + if (BoneNameIndex < TrackMap.Num() && TrackMap[BoneNameIndex].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = BoneNameIndex; + } + else + { + // This shouldn't happen but I need a fallback + // Don't currently want to reconstruct the map inversely + for (int i = 0; i < TrackMap.Num(); ++i) + { + if (TrackMap[i].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = i; + break; + } + } + } + + const FName& BoneName = PoseSnapShot.BoneNames[BoneNameIndex]; + + if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0)) + { + double TrackLocation = 0.0f; + HandTargetAnimation->GetBoneTransform(LocalTransform, FSkeletonPoseBoneIndex(TrackMap[TrackIndex].BoneTreeIndex), TrackLocation, false); + } + else + { + // otherwise, get ref pose if exists + const int32 BoneIDX = RefSkeleton.FindBoneIndex(BoneName); + if (BoneIDX != INDEX_NONE) + { + LocalTransform = RefSkeleton.GetRefBonePose()[BoneIDX]; + } + else + { + LocalTransform = FTransform::Identity; + } + } + + if (bUseCustomPoseDeltas) + { + FQuat DeltaQuat = FQuat::Identity; + if (FBPVRHandPoseBonePair* HandPair = CustomPoseDeltas.FindByKey(OrigBoneNames[BoneNameIndex])) + { + DeltaQuat = HandPair->DeltaPose; + } + + LocalTransform.ConcatenateRotation(DeltaQuat); + LocalTransform.NormalizeRotation(); + } + + if (bFlipHand && (!bSkipRootBone || TrackIndex != 0)) + { + FMatrix M = LocalTransform.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + LocalTransform.SetFromMatrix(M); + } + + PoseSnapShot.LocalTransforms.Add(LocalTransform); + } + + PoseSnapShot.bIsValid = true; + return true; + } + else if (bUseCustomPoseDeltas && CustomPoseDeltas.Num() && TargetMesh) + { + PoseSnapShot.SkeletalMeshName = TargetMesh->GetSkinnedAsset()->GetSkeleton()->GetFName(); + PoseSnapShot.SnapshotName = FName(TEXT("RawDeltaPose")); + PoseSnapShot.BoneNames.Empty(); + PoseSnapShot.LocalTransforms.Empty(); + TargetMesh->GetBoneNames(PoseSnapShot.BoneNames); + + PoseSnapShot.LocalTransforms = TargetMesh->GetSkinnedAsset()->GetSkeleton()->GetRefLocalPoses(); + + FQuat DeltaQuat = FQuat::Identity; + FName TargetBoneName = NAME_None; + + for (FBPVRHandPoseBonePair& HandPair : CustomPoseDeltas) + { + if (bFlipHand) + { + FString bName = HandPair.BoneName.ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + TargetBoneName = FName(bName); + } + else + { + TargetBoneName = HandPair.BoneName; + } + + int32 BoneIdx = TargetMesh->GetBoneIndex(TargetBoneName); + if (BoneIdx != INDEX_NONE) + { + DeltaQuat = HandPair.DeltaPose; + + if (bFlipHand) + { + FTransform DeltaTrans(DeltaQuat); + FMatrix M = DeltaTrans.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + DeltaTrans.SetFromMatrix(M); + DeltaQuat = DeltaTrans.GetRotation(); + } + + PoseSnapShot.LocalTransforms[BoneIdx].ConcatenateRotation(DeltaQuat); + PoseSnapShot.LocalTransforms[BoneIdx].NormalizeRotation(); + + } + } + + PoseSnapShot.bIsValid = true; + return true; + } + + return false; +} + +FTransform UHandSocketComponent::GetHandRelativePlacement() +{ + // Optionally mirror for left hand + + if (bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = GetAttachParent()) + { + return HandRelativePlacement.GetRelativeTransform(this->GetRelativeTransform()); + //FTransform curTrans = HandRelativePlacement * ParentComp->GetComponentTransform(); + //return curTrans.GetRelativeTransform(this->GetComponentTransform()); + } + } + + return HandRelativePlacement; +} + +FTransform UHandSocketComponent::GetHandSocketTransform(UGripMotionControllerComponent* QueryController, bool bIgnoreOnlySnapMesh) +{ + // Optionally mirror for left hand + + if (!bIgnoreOnlySnapMesh && bOnlySnapMesh) + { + if (!QueryController) + { + // No controller input + UE_LOG(LogVRMotionController, Warning, TEXT("HandSocketComponent::GetHandSocketTransform was missing required motion controller for bOnlySnapMesh! Check that you are passing a controller into GetClosestSocketInRange!")); + } + else + { + return QueryController->GetPivotTransform(); + } + } + + if (bFlipForLeftHand) + { + if (!QueryController) + { + // No controller input + UE_LOG(LogVRMotionController, Warning, TEXT("HandSocketComponent::GetHandSocketTransform was missing required motion controller for bFlipForLeftand! Check that you are passing a controller into GetClosestSocketInRange!")); + } + else + { + EControllerHand HandType; + QueryController->GetHandType(HandType); + bool bIsRightHand = HandType == EControllerHand::Right; + if (bLeftHandDominant == bIsRightHand) + { + FTransform ReturnTrans = this->GetRelativeTransform(); + if (USceneComponent* AttParent = this->GetAttachParent()) + { + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetAsEAxis(FlipAxis)); + if (bOnlyFlipRotation) + { + ReturnTrans.SetTranslation(this->GetRelativeLocation()); + } + + if (this->GetAttachSocketName() != NAME_None) + { + ReturnTrans = ReturnTrans * AttParent->GetSocketTransform(GetAttachSocketName(), RTS_Component); + } + + ReturnTrans = ReturnTrans * AttParent->GetComponentTransform(); + } + return ReturnTrans; + } + } + } + + return this->GetComponentTransform(); +} + +FTransform UHandSocketComponent::GetMeshRelativeTransform(bool bIsRightHand, bool bUseParentScale, bool bUseMirrorScale) +{ + // Optionally mirror for left hand + + // Failsafe + if (!this->GetAttachParent()) + return FTransform::Identity; + + FTransform relTrans = this->GetRelativeTransform(); + FTransform HandTrans = GetHandRelativePlacement(); + FTransform ReturnTrans = FTransform::Identity; + + // Fix the scale + if (!bUseParentScale && this->IsUsingAbsoluteScale() /*&& !bDecoupleMeshPlacement*/) + { + FVector ParentScale = this->GetAttachParent()->GetComponentScale(); + // Take parent scale out of our relative transform early + relTrans.ScaleTranslation(ParentScale); + ReturnTrans = HandTrans * relTrans; + // We add in the inverse of the parent scale to adjust the hand mesh + ReturnTrans.ScaleTranslation((FVector(1.0f) / ParentScale)); + ReturnTrans.SetScale3D(FVector(1.0f)); + } + else + { + ReturnTrans = HandTrans * relTrans; + } + + // If we should mirror the transform, do it now that it is in our parent relative space + if ((bFlipForLeftHand && (bLeftHandDominant == bIsRightHand))) + { + //FTransform relTrans = this->GetRelativeTransform(); + MirrorHandTransform(ReturnTrans, relTrans); + + if (bUseMirrorScale) + { + ReturnTrans.SetScale3D(ReturnTrans.GetScale3D() * MirroredScale.GetSignVector()); + } + } + + if (bIgnoreAttachBone && this->GetAttachSocketName() != NAME_None) + { + ReturnTrans = ReturnTrans * GetAttachParent()->GetSocketTransform(GetAttachSocketName(), RTS_Component); + } + + + return ReturnTrans; +} + +#if WITH_EDITORONLY_DATA +FTransform UHandSocketComponent::GetBoneTransformAtTime(UAnimSequence* MyAnimSequence, /*float AnimTime,*/ int BoneIdx, FName BoneName, bool bUseRawDataOnly) +{ + double tracklen = MyAnimSequence->GetPlayLength(); + FTransform BoneTransform = FTransform::Identity; + IAnimationDataController& AnimController = MyAnimSequence->GetController(); + + if (const IAnimationDataModel* AnimModel = AnimController.GetModel()) + { + const TArray& TrackMap = MyAnimSequence->GetCompressedTrackToSkeletonMapTable(); + + int32 TrackIndex = INDEX_NONE; + if (BoneIdx != INDEX_NONE && BoneIdx < TrackMap.Num() && TrackMap[BoneIdx].BoneTreeIndex == BoneIdx) + { + TrackIndex = BoneIdx; + } + else + { + // This shouldn't happen but I need a fallback + // Don't currently want to reconstruct the map inversely + for (int i = 0; i < TrackMap.Num(); ++i) + { + if (TrackMap[i].BoneTreeIndex == BoneIdx) + { + TrackIndex = i; + break; + } + } + } + + if (TrackIndex != INDEX_NONE) + { + FSkeletonPoseBoneIndex BoneIndex(TrackMap[TrackIndex].BoneTreeIndex); + if (BoneIndex.IsValid()) + { + MyAnimSequence->GetBoneTransform(BoneTransform, BoneIndex, /*AnimTime*/ tracklen, bUseRawDataOnly); + return BoneTransform; + } + } + else + { + return FTransform::Identity; + } + } + + return FTransform::Identity; +} +#endif + +void UHandSocketComponent::OnRegister() +{ + +#if WITH_EDITORONLY_DATA + AActor* MyOwner = GetOwner(); + if (bShowVisualizationMesh && (MyOwner != nullptr) && !IsRunningCommandlet()) + { + if (HandVisualizerComponent == nullptr && bShowVisualizationMesh) + { + HandVisualizerComponent = NewObject(MyOwner, NAME_None, RF_Transactional | RF_TextExportTransient); + HandVisualizerComponent->SetupAttachment(this); + HandVisualizerComponent->SetIsVisualizationComponent(true); + HandVisualizerComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + HandVisualizerComponent->CastShadow = false; + HandVisualizerComponent->CreationMethod = CreationMethod; + //HandVisualizerComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + HandVisualizerComponent->SetComponentTickEnabled(false); + HandVisualizerComponent->SetHiddenInGame(true); + HandVisualizerComponent->RegisterComponentWithWorld(GetWorld()); + //HandVisualizerComponent->SetUsingAbsoluteScale(true); + } + else if (!bShowVisualizationMesh && HandVisualizerComponent) + { + HideVisualizationMesh(); + } + + if (HandVisualizerComponent) + { + bTickedPose = false; + + if (VisualizationMesh) + { + if (HandPreviewMaterial) + { + HandVisualizerComponent->SetMaterial(0, (UMaterialInterface*)HandPreviewMaterial); + } + HandVisualizerComponent->SetSkinnedAssetAndUpdate(VisualizationMesh); + } + + PositionVisualizationMesh(); + PoseVisualizationToAnimation(true); + } + } + +#endif // WITH_EDITORONLY_DATA + + Super::OnRegister(); +} + +#if WITH_EDITORONLY_DATA + +void UHandSocketComponent::PositionVisualizationMesh() +{ + if (!HandVisualizerComponent) + { + return; + } + + if (USceneComponent* ParentAttach = this->GetAttachParent()) + { + FTransform relTrans = this->GetRelativeTransform(); + + if (bDecoupled != bDecoupleMeshPlacement) + { + if (bDecoupleMeshPlacement) + { + HandRelativePlacement = HandRelativePlacement * GetRelativeTransform(); + } + else + { + HandRelativePlacement = HandRelativePlacement.GetRelativeTransform(GetRelativeTransform()); + } + } + + FTransform HandPlacement = GetHandRelativePlacement(); + FTransform ReturnTrans = (HandPlacement * relTrans); + + if (bMirrorVisualizationMesh)//(bFlipForLeftHand && !bIsRightHand)) + { + MirrorHandTransform(ReturnTrans, relTrans); + } + + if ((bLeftHandDominant && !bMirrorVisualizationMesh) || (!bLeftHandDominant && bMirrorVisualizationMesh)) + { + ReturnTrans.SetScale3D(ReturnTrans.GetScale3D() * MirroredScale); + } + + HandVisualizerComponent->SetRelativeTransform(ReturnTrans.GetRelativeTransform(relTrans)/*newRel*/); + } +} + +void UHandSocketComponent::HideVisualizationMesh() +{ + if (!bShowVisualizationMesh && HandVisualizerComponent) + { + HandVisualizerComponent->SetVisibility(false); + HandVisualizerComponent->DestroyComponent(); + HandVisualizerComponent = nullptr; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void UHandSocketComponent::PoseVisualizationToAnimation(bool bForceRefresh) +{ + + if (!HandVisualizerComponent || !HandVisualizerComponent->GetSkinnedAsset()) + return; + + TArray LocalPoses; + if (!HandTargetAnimation) + { + // Store local poses for posing + LocalPoses = HandVisualizerComponent->GetSkinnedAsset()->GetSkeleton()->GetRefLocalPoses(); + } + + TArray BonesNames; + HandVisualizerComponent->GetBoneNames(BonesNames); + int32 Bones = HandVisualizerComponent->GetNumBones(); + + for (int32 i = 0; i < Bones; i++) + { + if (!HandTargetAnimation && !bUseCustomPoseDeltas) + { + if (HandVisualizerComponent->GetBoneSpaceTransforms().Num() > 0) + { + HandVisualizerComponent->ResetBoneTransformByName(BonesNames[i]); + } + continue; + } + + FName ParentBone = HandVisualizerComponent->GetParentBone(BonesNames[i]); + FTransform ParentTrans = FTransform::Identity; + if (ParentBone != NAME_None) + { + ParentTrans = HandVisualizerComponent->GetBoneTransformByName(ParentBone, EBoneSpaces::ComponentSpace); + } + + + FQuat DeltaQuat = FQuat::Identity; + if (bUseCustomPoseDeltas) + { + for (FBPVRHandPoseBonePair BonePairC : CustomPoseDeltas) + { + if (BonePairC.BoneName == BonesNames[i]) + { + DeltaQuat = BonePairC.DeltaPose; + DeltaQuat.Normalize(); + break; + } + } + } + + FTransform BoneTrans = FTransform::Identity; + + if (HandTargetAnimation) + { + BoneTrans = GetBoneTransformAtTime(HandTargetAnimation, /*FLT_MAX,*/ i, BonesNames[i], false); // true; + } + else + { + BoneTrans = LocalPoses[i]; + } + + BoneTrans = BoneTrans * ParentTrans;// *HandVisualizerComponent->GetComponentTransform(); + BoneTrans.NormalizeRotation(); + + //DeltaQuat *= HandVisualizerComponent->GetComponentTransform().GetRotation().Inverse(); + + BoneTrans.ConcatenateRotation(DeltaQuat); + BoneTrans.NormalizeRotation(); + HandVisualizerComponent->SetBoneTransformByName(BonesNames[i], BoneTrans, EBoneSpaces::ComponentSpace); + + } + + if (HandVisualizerComponent && (!bTickedPose || bForceRefresh)) + { + // Tick Pose first + if (HandVisualizerComponent->IsRegistered()) + { + bTickedPose = true; + HandVisualizerComponent->TickPose(1.0f, false); + if (HandVisualizerComponent->LeaderPoseComponent.IsValid()) + { + HandVisualizerComponent->UpdateFollowerComponent(); + } + else + { + HandVisualizerComponent->RefreshBoneTransforms(&HandVisualizerComponent->PrimaryComponentTick); + } + } + } +} + +void UHandSocketComponent::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) +{ + UHandSocketComponent* This = CastChecked(InThis); + Collector.AddReferencedObject(This->HandVisualizerComponent); + + Super::AddReferencedObjects(InThis, Collector); +} + +void UHandSocketComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (HandVisualizerComponent) + { + HandVisualizerComponent->DestroyComponent(); + } +} + +#endif + +void UHandSocketComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UHandSocketComponent, bRepGameplayTags); + DOREPLIFETIME(UHandSocketComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UHandSocketComponent, GameplayTags, COND_Custom); +} + +void UHandSocketComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UHandSocketComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +//============================================================================= +UHandSocketComponent::~UHandSocketComponent() +{ +} + +#if WITH_EDITOR + +void UHandSocketComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty* PropertyThatChanged = PropertyChangedEvent.Property; + + if (PropertyThatChanged != nullptr) + { +#if WITH_EDITORONLY_DATA + if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, VisualizationMesh) + ) + { + PositionVisualizationMesh(); + PoseVisualizationToAnimation(true); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas)) + { + PoseVisualizationToAnimation(true); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)) + { + PositionVisualizationMesh(); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bShowVisualizationMesh)) + { + HideVisualizationMesh(); + } +#endif + } +} +#endif + +UHandSocketComponent* UHandSocketComponent::GetHandSocketComponentFromObject(UObject* ObjectToCheck, FName SocketName) +{ + if (AActor* OwningActor = Cast(ObjectToCheck)) + { + if (USceneComponent* OwningRoot = Cast(OwningActor->GetRootComponent())) + { + TArray AttachChildren = OwningRoot->GetAttachChildren(); + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA() && AttachChild->GetFName() == SocketName) + { + return Cast(AttachChild); + } + } + } + } + else if (USceneComponent* OwningRoot = Cast(ObjectToCheck)) + { + TArray AttachChildren = OwningRoot->GetAttachChildren(); + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA() && AttachChild->GetFName() == SocketName) + { + return Cast(AttachChild); + } + } + } + + return nullptr; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp new file mode 100644 index 0000000..76ee525 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp @@ -0,0 +1,416 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRButtonComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRButtonComponent) + +#include "Net/UnrealNetwork.h" +//#include "VRGripInterface.h" +#include "GripMotionControllerComponent.h" +#include "GameFramework/Character.h" + + //============================================================================= +UVRButtonComponent::UVRButtonComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + LastToggleTime = 0.0f; + DepressDistance = 8.0f; + ButtonEngageDepth = 8.0f; + DepressSpeed = 50.0f; + + ButtonAxis = EVRInteractibleAxis::Axis_Z; + ButtonType = EVRButtonType::Btn_Toggle_Return; + + MinTimeBetweenEngaging = 0.1f; + + bIsEnabled = true; + StateChangeAuthorityType = EVRStateChangeAuthorityType::CanChangeState_All; + bButtonState = false; + + this->SetCollisionResponseToAllChannels(ECR_Overlap); + + bSkipOverlapFiltering = false; + InitialRelativeTransform = FTransform::Identity; + + bReplicateMovement = false; +} + +//============================================================================= +UVRButtonComponent::~UVRButtonComponent() +{ +} + +void UVRButtonComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRButtonComponent, InitialRelativeTransform); + DOREPLIFETIME(UVRButtonComponent, bReplicateMovement); + DOREPLIFETIME(UVRButtonComponent, StateChangeAuthorityType); + DOREPLIFETIME_CONDITION(UVRButtonComponent, bButtonState, COND_InitialOnly); +} + +void UVRButtonComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRButtonComponent, InitialRelativeTransform, bReplicateMovement); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRButtonComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialButtonLocation(); +} + +void UVRButtonComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + SetButtonToRestingPosition(); + + OnComponentBeginOverlap.AddUniqueDynamic(this, &UVRButtonComponent::OnOverlapBegin); + OnComponentEndOverlap.AddUniqueDynamic(this, &UVRButtonComponent::OnOverlapEnd); +} + +void UVRButtonComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + const float WorldTime = GetWorld()->GetRealTimeSeconds(); + + if (IsValid(LocalInteractingComponent)) + { + // If button was set to inactive during use + if (!bIsEnabled) + { + // Remove interacting component and return, next tick will begin lerping back + LocalInteractingComponent = nullptr; + return; + } + + FTransform OriginalBaseTransform = CalcNewComponentToWorld(InitialRelativeTransform); + + float CheckDepth = FMath::Clamp(GetAxisValue(InitialLocation) - GetAxisValue(OriginalBaseTransform.InverseTransformPosition(LocalInteractingComponent->GetComponentLocation())), 0.0f, DepressDistance); + + if (CheckDepth > 0.0f) + { + + float ClampMinDepth = 0.0f; + + // If active and a toggled stay, then clamp min to the toggled stay location + if (ButtonType == EVRButtonType::Btn_Toggle_Stay && bButtonState) + ClampMinDepth = -(ButtonEngageDepth + (1.e-2f)); // + NOT_SO_UE_KINDA_SMALL_NUMBER + + float NewDepth = FMath::Clamp(GetAxisValue(InitialComponentLoc) + (-CheckDepth), -DepressDistance, ClampMinDepth); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(SetAxisValue(NewDepth)), false); + + if (ButtonType == EVRButtonType::Btn_Toggle_Return || ButtonType == EVRButtonType::Btn_Toggle_Stay) + { + if ((StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_All) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Server && GetNetMode() < ENetMode::NM_Client) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Owner && IsValid(LocalLastInteractingActor) && LocalLastInteractingActor->HasLocalNetOwner())) + { + if (!bToggledThisTouch && NewDepth <= (-ButtonEngageDepth) + UE_KINDA_SMALL_NUMBER && (WorldTime - LastToggleTime) >= MinTimeBetweenEngaging) + { + LastToggleTime = WorldTime; + bToggledThisTouch = true; + bButtonState = !bButtonState; + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } + } + } + } + else + { + // Std precision tolerance should be fine + if (this->GetRelativeLocation().Equals(GetTargetRelativeLocation())) + { + this->SetComponentTickEnabled(false); + + OnButtonEndInteraction.Broadcast(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + ReceiveButtonEndInteraction(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + + LocalInteractingComponent = nullptr; // Just reset it here so it only does it once + LocalLastInteractingComponent = nullptr; + } + else + this->SetRelativeLocation(FMath::VInterpConstantTo(this->GetRelativeLocation(), GetTargetRelativeLocation(), DeltaTime, DepressSpeed), false); + } + + + // Press buttons always get checked, both during press AND during lerping for if they are active or not. + if (ButtonType == EVRButtonType::Btn_Press) + { + if ((StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_All) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Server && GetNetMode() < ENetMode::NM_Client) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Owner && IsValid(LocalLastInteractingActor) && LocalLastInteractingActor->HasLocalNetOwner())) + { + // Check for if we should set the state of the button, done here as for the press button the lerp counts for input + bool bCheckState = (GetAxisValue(InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation())) <= (-ButtonEngageDepth) + UE_KINDA_SMALL_NUMBER); + if (bButtonState != bCheckState && (WorldTime - LastToggleTime) >= MinTimeBetweenEngaging) + + { + LastToggleTime = WorldTime; + bButtonState = bCheckState; + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } + } + +} + +bool UVRButtonComponent::IsValidOverlap_Implementation(UPrimitiveComponent * OverlapComponent) +{ + + // Early out on the simple checks + if (!OverlapComponent || OverlapComponent == GetAttachParent() || OverlapComponent->GetAttachParent() == GetAttachParent()) + return false; + + // Should return faster checking for owning character + AActor * OverlapOwner = OverlapComponent->GetOwner(); + if (OverlapOwner && OverlapOwner->IsA(ACharacter::StaticClass())) + return true; + + // Because epic motion controllers are not owned by characters have to check here too in case someone implements it like that + // Now since our grip controllers are a subclass to the std ones we only need to check for the base one instead of both. + USceneComponent * OurAttachParent = OverlapComponent->GetAttachParent(); + if (OurAttachParent && OurAttachParent->IsA(UMotionControllerComponent::StaticClass())) + return true; + + // Now check for if it is a grippable object and if it is currently held + if (OverlapComponent->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapComponent, Controllers, bIsHeld); + + if (bIsHeld) + return true; + } + else if(OverlapOwner && OverlapOwner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapOwner, Controllers, bIsHeld); + + if (bIsHeld) + return true; + } + + return false; +} + +void UVRButtonComponent::SetLastInteractingActor() +{ + + // Early out on the simple checks + if (!IsValid(LocalInteractingComponent) || LocalInteractingComponent == GetAttachParent() || LocalInteractingComponent->GetAttachParent() == GetAttachParent()) + { + LocalLastInteractingActor = nullptr; + LocalLastInteractingComponent = nullptr; + return; + } + + LocalLastInteractingComponent = LocalInteractingComponent; + + // Should return faster checking for owning character + AActor * OverlapOwner = LocalInteractingComponent->GetOwner(); + if (OverlapOwner && OverlapOwner->IsA(ACharacter::StaticClass())) + { + LocalLastInteractingActor = OverlapOwner; + return; + } + + // Now check for if it is a grippable object and if it is currently held + if (LocalInteractingComponent->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(LocalLastInteractingComponent.Get(), Controllers, bIsHeld); + + if (bIsHeld && Controllers.Num()) + { + AActor * ControllerOwner = Controllers[0].HoldingController != nullptr ? Controllers[0].HoldingController->GetOwner() : nullptr; + if (ControllerOwner) + { + LocalLastInteractingActor = ControllerOwner; + return; + } + } + } + else if (OverlapOwner && OverlapOwner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapOwner, Controllers, bIsHeld); + + if (bIsHeld && Controllers.Num()) + { + AActor * ControllerOwner = Controllers[0].HoldingController != nullptr ? Controllers[0].HoldingController->GetOwner() : nullptr; + if (ControllerOwner) + { + LocalLastInteractingActor = ControllerOwner; + return; + } + } + } + + // Fall back to the owner, wasn't held and wasn't a character + if (OverlapOwner) + { + LocalLastInteractingActor = OverlapOwner; + return; + } + + LocalLastInteractingActor = nullptr; + return; +} + +void UVRButtonComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + // Other Actor is the actor that triggered the event. Check that is not ourself. + if (bIsEnabled && !IsValid(LocalInteractingComponent) && (bSkipOverlapFiltering || IsValidOverlap(OtherComp))) + { + LocalInteractingComponent = OtherComp; + + FTransform OriginalBaseTransform = CalcNewComponentToWorld(InitialRelativeTransform); + FVector loc = LocalInteractingComponent->GetComponentLocation(); + InitialLocation = OriginalBaseTransform.InverseTransformPosition(LocalInteractingComponent->GetComponentLocation()); + InitialComponentLoc = OriginalBaseTransform.InverseTransformPosition(this->GetComponentLocation()); + bToggledThisTouch = false; + + this->SetComponentTickEnabled(true); + + if (LocalInteractingComponent != LocalLastInteractingComponent.Get()) + { + SetLastInteractingActor(); + OnButtonBeginInteraction.Broadcast(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + ReceiveButtonBeginInteraction(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } +} + +void UVRButtonComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if (IsValid(LocalInteractingComponent) && OtherComp == LocalInteractingComponent) + { + LocalInteractingComponent = nullptr; + } +} + +FVector UVRButtonComponent::GetTargetRelativeLocation() +{ + // If target is the half pressed + if (ButtonType == EVRButtonType::Btn_Toggle_Stay && bButtonState) + { + // 1.e-2f = MORE_UE_KINDA_SMALL_NUMBER + return InitialRelativeTransform.TransformPosition(SetAxisValue(-(ButtonEngageDepth + (1.e-2f)))); + } + + // Else return going all the way back + return InitialRelativeTransform.GetTranslation(); + +} + +void UVRButtonComponent::SetButtonToRestingPosition(bool bLerpToPosition) +{ + switch (ButtonType) + { + case EVRButtonType::Btn_Press: + { + }break; + case EVRButtonType::Btn_Toggle_Return: + {}break; + case EVRButtonType::Btn_Toggle_Stay: + { + if (!bLerpToPosition) + { + float ClampMinDepth = 0.0f; + + // If active and a toggled stay, then clamp min to the toggled stay location + if (bButtonState) + ClampMinDepth = -(ButtonEngageDepth + (1.e-2f)); // + NOT_SO_UE_KINDA_SMALL_NUMBER + + float NewDepth = FMath::Clamp(ClampMinDepth, -DepressDistance, ClampMinDepth); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(SetAxisValue(NewDepth)), false); + } + else + this->SetComponentTickEnabled(true); // This will trigger the lerp to resting position + + }break; + default:break; + } +} + +void UVRButtonComponent::SetButtonState(bool bNewButtonState, bool bCallButtonChangedEvent, bool bSnapIntoPosition) +{ + // No change + if (bButtonState == bNewButtonState) + return; + + bButtonState = bNewButtonState; + SetButtonToRestingPosition(!bSnapIntoPosition); + LastToggleTime = GetWorld()->GetRealTimeSeconds(); + + if (bCallButtonChangedEvent) + { + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } +} + +void UVRButtonComponent::ResetInitialButtonLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); +} + +bool UVRButtonComponent::IsButtonInUse() +{ + return IsValid(LocalInteractingComponent); +} + +float UVRButtonComponent::GetAxisValue(FVector CheckLocation) +{ + switch (ButtonAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckLocation.X; break; + case EVRInteractibleAxis::Axis_Y: + return CheckLocation.Y; break; + case EVRInteractibleAxis::Axis_Z: + return CheckLocation.Z; break; + default:return 0.0f; break; + } +} + +FVector UVRButtonComponent::SetAxisValue(float SetValue) +{ + FVector vec = FVector::ZeroVector; + + switch (ButtonAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.X = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Y = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Z = SetValue; break; + } + + return vec; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp new file mode 100644 index 0000000..766f5e7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp @@ -0,0 +1,596 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRDialComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRDialComponent) + +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRDialComponent::UVRDialComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + DialRotationAxis = EVRInteractibleAxis::Axis_Z; + InteractorRotationAxis = EVRInteractibleAxis::Axis_X; + + bDialUsesAngleSnap = false; + bDialUseSnapAngleList = false; + SnapAngleThreshold = 45.0f; + SnapAngleIncrement = 45.0f; + LastSnapAngle = 0.0f; + RotationScaler = 1.0f; + + ClockwiseMaximumDialAngle = 180.0f; + CClockwiseMaximumDialAngle = 180.0f; + bDenyGripping = false; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + + bLerpBackOnRelease = false; + bSendDialEventsDuringLerp = false; + DialReturnSpeed = 90.0f; + bIsLerping = false; + + bDialUseDirectHandRotation = false; + LastGripRot = 0.0f; + InitialGripRot = 0.f; + InitialRotBackEnd = 0.f; + bUseRollover = false; +} + +//============================================================================= +UVRDialComponent::~UVRDialComponent() +{ +} + + +void UVRDialComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRDialComponent, InitialRelativeTransform); + //DOREPLIFETIME_CONDITION(UVRDialComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRDialComponent, bRepGameplayTags); + DOREPLIFETIME(UVRDialComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRDialComponent, GameplayTags, COND_Custom); +} + +void UVRDialComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UVRDialComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRDialComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialDialLocation(); // Load the original dial location +} + +void UVRDialComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + CalculateDialProgress(); + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRDialComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (bIsLerping) + { + if (bUseRollover) + { + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 0.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + } + else + { + // Flip lerp direction if we are on the other side + if (CurrentDialAngle > ClockwiseMaximumDialAngle) + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 360.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + else + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 0.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + } + + if (CurRotBackEnd == 0.f) + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + OnDialFinishedLerping.Broadcast(); + ReceiveDialFinishedLerping(); + } + } + else + { + this->SetComponentTickEnabled(false); + } +} + +void UVRDialComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + + // #TODO: Should this use a pivot rotation? it wouldn't make that much sense to me? + float DeltaRot = 0.0f; + + if (!bDialUseDirectHandRotation) + { + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + float NewRot = FRotator::ClampAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(DialRotationAxis, CurInteractorLocation)); + //DeltaRot = RotationScaler * ( NewRot - LastGripRot); + + DeltaRot = RotationScaler * FMath::FindDeltaAngleDegrees(LastGripRot, NewRot); + + float LimitTest = FRotator::ClampAxis(((NewRot - InitialGripRot) + InitialRotBackEnd)); + float MaxCheckValue = bUseRollover ? -CClockwiseMaximumDialAngle : 360.0f - CClockwiseMaximumDialAngle; + + if (FMath::IsNearlyZero(CClockwiseMaximumDialAngle)) + { + if (LimitTest > ClockwiseMaximumDialAngle && (CurRotBackEnd == ClockwiseMaximumDialAngle || CurRotBackEnd == 0.f)) + { + DeltaRot = 0.f; + } + } + else if (FMath::IsNearlyZero(ClockwiseMaximumDialAngle)) + { + if (LimitTest < MaxCheckValue && (CurRotBackEnd == MaxCheckValue || CurRotBackEnd == 0.f)) + { + DeltaRot = 0.f; + } + } + else if (LimitTest > ClockwiseMaximumDialAngle && LimitTest < MaxCheckValue && (CurRotBackEnd == ClockwiseMaximumDialAngle || CurRotBackEnd == MaxCheckValue)) + { + DeltaRot = 0.f; + } + + LastGripRot = NewRot; + } + else + { + FRotator curRotation = GrippingController->GetComponentRotation(); + DeltaRot = RotationScaler * UVRInteractibleFunctionLibrary::GetAxisValue(InteractorRotationAxis, (curRotation - LastRotation).GetNormalized()); + LastRotation = curRotation; + } + + AddDialAngle(DeltaRot, true); + + // Handle the auto drop + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return; + } +} + +void UVRDialComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform CurrentTransform = this->GetComponentTransform(); + FTransform RelativeToGripTransform = ReversedRelativeTransform * CurrentTransform; + + //FTransform InitialTrans = RelativeToGripTransform.GetRelativeTransform(CurrentRelativeTransform); + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialDropLocation = ReversedRelativeTransform.GetTranslation(); + + if (!bDialUseDirectHandRotation) + { + LastGripRot = FRotator::ClampAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(DialRotationAxis, InitialInteractorLocation)); + InitialGripRot = LastGripRot; + InitialRotBackEnd = CurRotBackEnd; + } + else + { + LastRotation = RelativeToGripTransform.GetRotation().Rotator(); // Forcing into world space now so that initial can be correct over the network + } + + bIsLerping = false; + + //OnGripped.Broadcast(GrippingController, GripInformation); +} + +void UVRDialComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if (bDialUsesAngleSnap && bDialUseSnapAngleList) + { + float closestAngle = 0.f; + float closestVal = FMath::Abs(closestAngle - CurRotBackEnd); + float closestValt = 0.f; + for (float val : DialSnapAngleList) + { + closestValt = FMath::Abs(val - CurRotBackEnd); + if (closestValt < closestVal) + { + closestAngle = val; + closestVal = closestValt; + } + } + + if (closestAngle != LastSnapAngle) + { + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(closestAngle), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(closestAngle); + CurRotBackEnd = CurrentDialAngle; + + if (!FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + LastSnapAngle = CurrentDialAngle; + } + } + } + else if (bDialUsesAngleSnap && SnapAngleIncrement > 0.f && FMath::Abs(FMath::Fmod(CurRotBackEnd, SnapAngleIncrement)) <= FMath::Min(SnapAngleIncrement, SnapAngleThreshold)) + { + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurRotBackEnd = FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement); + + if (bUseRollover) + { + CurrentDialAngle = FMath::RoundToFloat(CurRotBackEnd); + } + else + { + CurrentDialAngle = FRotator::ClampAxis(FMath::RoundToFloat(CurRotBackEnd)); + } + + if (!FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + LastSnapAngle = CurrentDialAngle; + } + } + + if (bLerpBackOnRelease) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + } + else + this->SetComponentTickEnabled(false); + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRDialComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRDialComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRDialComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnUsed_Implementation() {} +void UVRDialComponent::OnEndUsed_Implementation() {} +void UVRDialComponent::OnSecondaryUsed_Implementation() {} +void UVRDialComponent::OnEndSecondaryUsed_Implementation() {} +void UVRDialComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRDialComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRDialComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRDialComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRDialComponent::SimulateOnDrop_Implementation() +{ + return false; +} + +/*EGripCollisionType UVRDialComponent::SlotGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +} + +EGripCollisionType UVRDialComponent::FreeGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +}*/ + +EGripCollisionType UVRDialComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRDialComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRDialComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRDialComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRDialComponent::GripStiffness_Implementation() +{ + return 1500.0f; +} + +float UVRDialComponent::GripDamping_Implementation() +{ + return 200.0f; +}*/ + +void UVRDialComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = 0.0f; + GripDampingOut = 0.0f; +} + +FBPAdvGripSettings UVRDialComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRDialComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRDialComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRDialComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRDialComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRDialComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRDialComponent::IsHeld_Implementation(TArray & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRDialComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRDialComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if(!bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRDialComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRDialComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + return false; +} + +void UVRDialComponent::SetDialAngle(float DialAngle, bool bCallEvents) +{ + CurRotBackEnd = DialAngle; + AddDialAngle(0.0f, bCallEvents); +} + +void UVRDialComponent::AddDialAngle(float DialAngleDelta, bool bCallEvents, bool bSkipSettingRot) +{ + //FindDeltaAngleDegrees + /** Utility to ensure angle is between +/- 180 degrees by unwinding. */ +//static float UnwindDegrees(float A) + float MaxCheckValue = bUseRollover ? -CClockwiseMaximumDialAngle : 360.0f - CClockwiseMaximumDialAngle; + + float DeltaRot = DialAngleDelta; + float tempCheck = bUseRollover ? CurRotBackEnd + DeltaRot : FRotator::ClampAxis(CurRotBackEnd + DeltaRot); + + // Clamp it to the boundaries + if (FMath::IsNearlyZero(CClockwiseMaximumDialAngle)) + { + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, 0.0f, ClockwiseMaximumDialAngle); + } + else if (FMath::IsNearlyZero(ClockwiseMaximumDialAngle)) + { + if (bUseRollover) + { + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, -CClockwiseMaximumDialAngle, 0.0f); + } + else + { + if (CurRotBackEnd < MaxCheckValue) + CurRotBackEnd = FMath::Clamp(360.0f + DeltaRot, MaxCheckValue, 360.0f); + else + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, MaxCheckValue, 360.0f); + } + } + else if(!bUseRollover && tempCheck > ClockwiseMaximumDialAngle && tempCheck < MaxCheckValue) + { + if (CurRotBackEnd < MaxCheckValue) + { + CurRotBackEnd = ClockwiseMaximumDialAngle; + } + else + { + CurRotBackEnd = MaxCheckValue; + } + } + else if (bUseRollover) + { + if (tempCheck > ClockwiseMaximumDialAngle) + { + CurRotBackEnd = ClockwiseMaximumDialAngle; + } + else if (tempCheck < MaxCheckValue) + { + CurRotBackEnd = MaxCheckValue; + } + else + { + CurRotBackEnd = tempCheck; + } + } + else + { + CurRotBackEnd = tempCheck; + } + + if (bDialUsesAngleSnap && bDialUseSnapAngleList) + { + float closestAngle = 0.f; + // Always default 0.0f to the list + float closestVal = FMath::Abs(closestAngle - CurRotBackEnd); + float closestValt = 0.f; + for (float val : DialSnapAngleList) + { + closestValt = FMath::Abs(val - CurRotBackEnd); + if (closestValt < closestVal) + { + closestAngle = val; + closestVal = closestValt; + } + } + + if (closestAngle != LastSnapAngle) + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(closestAngle), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(closestAngle); + + if (bCallEvents && !FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + } + + LastSnapAngle = CurrentDialAngle; + } + } + else if (bDialUsesAngleSnap && SnapAngleIncrement > 0.f && FMath::Abs(FMath::Fmod(CurRotBackEnd, SnapAngleIncrement)) <= FMath::Min(SnapAngleIncrement, SnapAngleThreshold)) + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement)), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement)); + + if (bCallEvents && !FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + } + + LastSnapAngle = CurrentDialAngle; + } + else + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(CurRotBackEnd), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(CurRotBackEnd); + } + +} + +void UVRDialComponent::ResetInitialDialLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + CurRotBackEnd = 0.0f; + CalculateDialProgress(); +} + +void UVRDialComponent::CalculateDialProgress() +{ + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + LastGripRot = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms(DialRotationAxis, InitialRelativeTransform, CurRelativeTransform); + CurRotBackEnd = LastGripRot; + AddDialAngle(0.0f, false, true); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp new file mode 100644 index 0000000..4ec29f9 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp @@ -0,0 +1,8 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRInteractibleFunctionLibrary) + +//#include "Engine/Engine.h" + +//General Log +DEFINE_LOG_CATEGORY(VRInteractibleFunctionLibraryLog); \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp new file mode 100644 index 0000000..93093d8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp @@ -0,0 +1,819 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRLeverComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRLeverComponent) + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRLeverComponent::UVRLeverComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + Stiffness = 1500.0f; + Damping = 200.0f; + + //SceneIndex = 0; + + ParentComponent = nullptr; + LeverRotationAxis = EVRInteractibleLeverAxis::Axis_X; + + LeverLimitNegative = 0.0f; + LeverLimitPositive = 90.0f; + FlightStickYawLimit = 180.0f; + bLeverState = false; + LeverTogglePercentage = 0.8f; + + LastDeltaAngle = 0.0f; + FullCurrentAngle = 0.0f; + + LeverReturnTypeWhenReleased = EVRInteractibleLeverReturnType::ReturnToZero; + LeverReturnSpeed = 50.0f; + + MomentumAtDrop = 0.0f; + LeverMomentumFriction = 5.0f; + MaxLeverMomentum = 180.0f; + FramesToAverage = 3; + + bBlendAxisValuesByAngleThreshold = false; + AngleThreshold = 90.0f; + + LastLeverAngle = 0.0f; + + bSendLeverEventsDuringLerp = false; + + InitialRelativeTransform = FTransform::Identity; + InitialInteractorLocation = FVector::ZeroVector; + InteractorOffsetTransform = FTransform::Identity; + AllCurrentLeverAngles = FRotator::ZeroRotator; + InitialGripRot = 0.0f; + qRotAtGrab = FQuat::Identity; + bIsLerping = false; + bUngripAtTargetRotation = false; + bDenyGripping = false; + + bIsLocked = false; + bAutoDropWhenLocked = true; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRLeverComponent::~UVRLeverComponent() +{ +} + + +void UVRLeverComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRLeverComponent, InitialRelativeTransform); + //DOREPLIFETIME_CONDITION(UVRLeverComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRLeverComponent, bRepGameplayTags); + DOREPLIFETIME(UVRLeverComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRLeverComponent, GameplayTags, COND_Custom); +} + +void UVRLeverComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRLeverComponent, InitialRelativeTransform, bReplicateMovement); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UVRLeverComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRLeverComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialLeverLocation(); // Load the original lever location +} + +void UVRLeverComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + ReCalculateCurrentAngle(true); + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRLeverComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + bool bWasLerping = bIsLerping; + + // If we are locked then end the lerp, no point + if (bIsLocked) + { + + if (bWasLerping) + { + bIsLerping = false; + + // If we start lerping while locked, just end it + OnLeverFinishedLerping.Broadcast(CurrentLeverAngle); + ReceiveLeverFinishedLerping(CurrentLeverAngle); + } + + return; + } + + if (bIsLerping) + { + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + case EVRInteractibleLeverAxis::Axis_Z: + { + LerpAxis(FullCurrentAngle, DeltaTime); + }break; + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + // Only supporting LerpToZero with this mode currently + FQuat LerpedQuat = FMath::QInterpConstantTo(CurRelativeTransform.GetRelativeTransform(InitialRelativeTransform).GetRotation(), FQuat::Identity, DeltaTime, FMath::DegreesToRadians(LeverReturnSpeed)); + + if (LerpedQuat.IsIdentity()) + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + this->SetRelativeRotation(InitialRelativeTransform.Rotator()); + } + else + { + this->SetRelativeRotation((FTransform(LerpedQuat) * InitialRelativeTransform).GetRotation()); + } + }break; + default:break; + } + } + + FTransform CurrentRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurrentRelativeTransform); + + + if (!bWasLerping && LeverReturnTypeWhenReleased == EVRInteractibleLeverReturnType::RetainMomentum) + { + // Rolling average across num samples + MomentumAtDrop -= MomentumAtDrop / FramesToAverage; + MomentumAtDrop += ((FullCurrentAngle - LastLeverAngle) / DeltaTime) / FramesToAverage; + + MomentumAtDrop = FMath::Min(MaxLeverMomentum, MomentumAtDrop); + + LastLeverAngle = FullCurrentAngle; + } + + // Check for events and set current state and check for auto drop + ProccessCurrentState(bWasLerping, true, true); + + // If the lerping state changed from the above + if (bWasLerping && !bIsLerping) + { + OnLeverFinishedLerping.Broadcast(CurrentLeverAngle); + ReceiveLeverFinishedLerping(CurrentLeverAngle); + } +} + +void UVRLeverComponent::ProccessCurrentState(bool bWasLerping, bool bThrowEvents, bool bCheckAutoDrop) +{ + bool bNewLeverState = (!FMath::IsNearlyZero(LeverLimitNegative) && FullCurrentAngle <= -(LeverLimitNegative * LeverTogglePercentage)) || (!FMath::IsNearlyZero(LeverLimitPositive) && FullCurrentAngle >= (LeverLimitPositive * LeverTogglePercentage)); + //if (FMath::Abs(CurrentLeverAngle) >= LeverLimit ) + if (bNewLeverState != bLeverState) + { + bLeverState = bNewLeverState; + + if (bThrowEvents && (bSendLeverEventsDuringLerp || !bWasLerping)) + { + ReceiveLeverStateChanged(bLeverState, FullCurrentAngle >= 0.0f ? EVRInteractibleLeverEventType::LeverPositive : EVRInteractibleLeverEventType::LeverNegative, CurrentLeverAngle, FullCurrentAngle); + OnLeverStateChanged.Broadcast(bLeverState, FullCurrentAngle >= 0.0f ? EVRInteractibleLeverEventType::LeverPositive : EVRInteractibleLeverEventType::LeverNegative, CurrentLeverAngle, FullCurrentAngle); + } + + if (bCheckAutoDrop) + { + if (!bWasLerping && bUngripAtTargetRotation && bLeverState && HoldingGrip.IsValid()) + { + FBPActorGripInformation GripInformation; + EBPVRResultSwitch result; + HoldingGrip.HoldingController->GetGripByID(GripInformation, HoldingGrip.GripID, result); + if (result == EBPVRResultSwitch::OnSucceeded && HoldingGrip.HoldingController->HasGripAuthority(GripInformation)) + { + HoldingGrip.HoldingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + } + } + } +} + +void UVRLeverComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +bool UVRLeverComponent::CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + // Converted to a relative value now so it should be correct + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialInteractorDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + + return true; + } + + return false; +} + +void UVRLeverComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + if (bIsLocked) + { + if (bAutoDropWhenLocked) + { + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); + } + + return; + } + + // Handle manual tracking here + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FTransform PivotTransform = GrippingController->GetPivotTransform(); + FVector CurInteractorLocation = (InteractorOffsetTransform * PivotTransform).GetRelativeTransform(CurrentRelativeTransform).GetTranslation(); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + FRotator Rot; + + FVector nAxis; + float nAngle = 0.0f; + + FQuat::FindBetweenVectors(qRotAtGrab.UnrotateVector(InitialInteractorLocation), CurInteractorLocation).ToAxisAndAngle(nAxis, nAngle); + float MaxAngle = FMath::DegreesToRadians(LeverLimitPositive); + + bool bWasClamped = nAngle > MaxAngle; + nAngle = FMath::Clamp(nAngle, 0.0f, MaxAngle); + Rot = FQuat(nAxis, nAngle).Rotator(); + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + // Store our projected relative transform + FTransform CalcTransform = (FTransform(Rot) * InitialRelativeTransform); + + // Fixup yaw if this is a flight stick + + if (bWasClamped) + { + // If we clamped the angle due to limits then lets re-project the hand back to get the correct facing again + // This is only when things have been clamped to avoid the extra calculations + FTransform NewPivTrans = PivotTransform.GetRelativeTransform((CalcTransform * ParentTransform)); + CurInteractorLocation = NewPivTrans.GetTranslation(); + + FVector OffsetVal = CurInteractorLocation + NewPivTrans.GetRotation().RotateVector(InteractorOffsetTransform.GetTranslation()); + OffsetVal.Z = 0; + + CurInteractorLocation -= OffsetVal; + } + else + { + CurInteractorLocation = (CalcTransform * ParentTransform).InverseTransformPosition(GrippingController->GetPivotLocation()); + } + + float CurrentLeverYawAngle = FRotator::NormalizeAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(EVRInteractibleAxis::Axis_Z, CurInteractorLocation, InitialGripRot)); + + if (FlightStickYawLimit < 180.0f) + { + CurrentLeverYawAngle = FMath::Clamp(CurrentLeverYawAngle, -FlightStickYawLimit, FlightStickYawLimit); + } + + FQuat newLocalRot = CalcTransform.GetRotation() * FQuat(FVector::UpVector, FMath::DegreesToRadians(CurrentLeverYawAngle)); + this->SetRelativeRotation(newLocalRot.Rotator()); + } + else + { + this->SetRelativeRotation((FTransform(Rot) * InitialRelativeTransform).Rotator()); + } + } + break; + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + case EVRInteractibleLeverAxis::Axis_Z: + { + float DeltaAngle = CalcAngle(LeverRotationAxis, CurInteractorLocation); + LastDeltaAngle = DeltaAngle; + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, DeltaAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + }break; + default:break; + } + + // Recalc current angle + CurrentRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurrentRelativeTransform); + + // Check for events and set current state and check for auto drop + ProccessCurrentState(bIsLerping, true, true); + + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); +} + +void UVRLeverComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + ParentComponent = this->GetAttachParent(); + + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform CurrentTransform = this->GetComponentTransform(); + FTransform RelativeToGripTransform = FTransform::Identity; + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + // Offset the grip to the same height on the cross axis and centered on the lever + FVector InitialInteractorOffset = ReversedRelativeTransform.GetTranslation(); + FTransform InitTrans = ReversedRelativeTransform; + InitialInteractorOffset.X = 0; + InitialInteractorOffset.Y = 0; + InteractorOffsetTransform = ReversedRelativeTransform; + InteractorOffsetTransform.AddToTranslation(-InitialInteractorOffset); + InteractorOffsetTransform = FTransform(InteractorOffsetTransform.ToInverseMatrixWithScale()); + + InitialInteractorOffset = ReversedRelativeTransform.GetTranslation(); + InitialInteractorOffset.Z = 0; + + InitTrans.AddToTranslation(-InitialInteractorOffset); + RelativeToGripTransform = InitTrans * CurrentTransform; + } + else + { + RelativeToGripTransform = ReversedRelativeTransform * CurrentTransform; + InteractorOffsetTransform = FTransform::Identity; + } + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialInteractorDropLocation = ReversedRelativeTransform.GetTranslation(); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + { + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + }break; + case EVRInteractibleLeverAxis::FlightStick_XY: + { + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle(EVRInteractibleAxis::Axis_Z, ReversedRelativeTransform.GetTranslation()); + }break; + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + { + // Get our initial interactor rotation + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)LeverRotationAxis, InitialInteractorLocation); + }break; + + case EVRInteractibleLeverAxis::Axis_Z: + { + // Get our initial interactor rotation + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)LeverRotationAxis, InitialInteractorLocation); + }break; + + default:break; + } + + // Get out current rotation at grab + RotAtGrab = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms((EVRInteractibleAxis)LeverRotationAxis, CurrentRelativeTransform, CurrentTransform); + + LastLeverAngle = CurrentLeverAngle; + bIsLerping = false; + bIsInFirstTick = true; + MomentumAtDrop = 0.0f; + + this->SetComponentTickEnabled(true); + + //OnGripped.Broadcast(GrippingController, GripInformation); +} + +void UVRLeverComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if (LeverReturnTypeWhenReleased != EVRInteractibleLeverReturnType::Stay) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + bReplicateMovement = false; + } + else + { + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRLeverComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRLeverComponent::SetIsLocked(bool bNewLockedState) +{ + bIsLocked = bNewLockedState; +} + +void UVRLeverComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRLeverComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnUsed_Implementation() {} +void UVRLeverComponent::OnEndUsed_Implementation() {} +void UVRLeverComponent::OnSecondaryUsed_Implementation() {} +void UVRLeverComponent::OnEndSecondaryUsed_Implementation() {} +void UVRLeverComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRLeverComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRLeverComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRLeverComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRLeverComponent::SimulateOnDrop_Implementation() +{ + return false; +} + + +EGripCollisionType UVRLeverComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRLeverComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRLeverComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRLeverComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRLeverComponent::GripStiffness_Implementation() +{ + return Stiffness; +} + +float UVRLeverComponent::GripDamping_Implementation() +{ + return Damping; +}*/ +void UVRLeverComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = Stiffness; + GripDampingOut = Damping; +} + +FBPAdvGripSettings UVRLeverComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRLeverComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRLeverComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRLeverComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRLeverComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRLeverComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRLeverComponent::IsHeld_Implementation(TArray & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRLeverComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRLeverComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld && !bIsLerping) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRLeverComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRLeverComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + return false; +} + +float UVRLeverComponent::ReCalculateCurrentAngle(bool bAllowThrowingEvents) +{ + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurRelativeTransform); + ProccessCurrentState(bIsLerping, bAllowThrowingEvents, bAllowThrowingEvents); + return CurrentLeverAngle; +} + +void UVRLeverComponent::SetLeverAngle(float NewAngle, FVector DualAxisForwardVector, bool bAllowThrowingEvents) +{ + NewAngle = -NewAngle; // Need to inverse the sign + + FVector ForwardVector = DualAxisForwardVector; + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_X: + ForwardVector = FVector(FMath::Sign(NewAngle), 0.0f, 0.0f); break; + case EVRInteractibleLeverAxis::Axis_Y: + ForwardVector = FVector(0.0f, FMath::Sign(NewAngle), 0.0f); break; + case EVRInteractibleLeverAxis::Axis_Z: + ForwardVector = FVector(0.0f, 0.0f, FMath::Sign(NewAngle)); break; + default:break; + } + + FQuat NewLeverRotation(ForwardVector, FMath::DegreesToRadians(FMath::Abs(NewAngle))); + + this->SetRelativeTransform(FTransform(NewLeverRotation) * InitialRelativeTransform); + ReCalculateCurrentAngle(bAllowThrowingEvents); +} + +void UVRLeverComponent::ResetInitialLeverLocation(bool bAllowThrowingEvents) +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + CalculateCurrentAngle(InitialRelativeTransform); + ProccessCurrentState(bIsLerping, bAllowThrowingEvents, bAllowThrowingEvents); +} + +void UVRLeverComponent::CalculateCurrentAngle(FTransform & CurrentTransform) +{ + float Angle; + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + FTransform RelativeToSpace = CurrentTransform.GetRelativeTransform(InitialRelativeTransform); + FQuat CurrentRelRot = RelativeToSpace.GetRotation();// CurrentTransform.GetRotation(); + FVector UpVec = CurrentRelRot.GetUpVector(); + + CurrentLeverForwardVector = FVector::VectorPlaneProject(UpVec, FVector::UpVector); + CurrentLeverForwardVector.Normalize(); + + FullCurrentAngle = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(UpVec, FVector::UpVector))); + CurrentLeverAngle = FMath::RoundToFloat(FullCurrentAngle); + + AllCurrentLeverAngles.Roll = FMath::Sign(UpVec.Y) * FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(FVector(0.0f, UpVec.Y, UpVec.Z), FVector::UpVector))); + AllCurrentLeverAngles.Pitch = FMath::Sign(UpVec.X) * FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(FVector(UpVec.X, 0.0f, UpVec.Z), FVector::UpVector))); + + if (bBlendAxisValuesByAngleThreshold) + { + FVector ProjectedLoc = FVector(UpVec.X, UpVec.Y, 0.0f).GetSafeNormal(); + AllCurrentLeverAngles.Pitch *= FMath::Clamp(1.0f - (FMath::Abs(FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ProjectedLoc, FMath::Sign(UpVec.X) * FVector::ForwardVector)))) / AngleThreshold), 0.0f, 1.0f); + AllCurrentLeverAngles.Roll *= FMath::Clamp(1.0f - (FMath::Abs(FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ProjectedLoc, FMath::Sign(UpVec.Y) * FVector::RightVector)))) / AngleThreshold), 0.0f, 1.0f); + } + + AllCurrentLeverAngles.Roll = FMath::RoundToFloat(AllCurrentLeverAngles.Roll); + AllCurrentLeverAngles.Pitch = FMath::RoundToFloat(AllCurrentLeverAngles.Pitch); + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + AllCurrentLeverAngles.Yaw = FRotator::NormalizeAxis(FMath::RoundToFloat(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(CurrentRelRot.Rotator()).Yaw)); + if (FlightStickYawLimit < 180.0f) + { + AllCurrentLeverAngles.Yaw = FMath::Clamp(AllCurrentLeverAngles.Yaw, -FlightStickYawLimit, FlightStickYawLimit); + } + } + else + AllCurrentLeverAngles.Yaw = 0.0f; + + }break; + default: + { + Angle = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms((EVRInteractibleAxis)LeverRotationAxis, InitialRelativeTransform, CurrentTransform); + FullCurrentAngle = Angle; + CurrentLeverAngle = FMath::RoundToFloat(FullCurrentAngle); + CurrentLeverForwardVector = UVRInteractibleFunctionLibrary::SetAxisValueVec((EVRInteractibleAxis)LeverRotationAxis, FMath::Sign(Angle)); + AllCurrentLeverAngles = UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, CurrentLeverAngle, FRotator::ZeroRotator); + + }break; + } +} + +void UVRLeverComponent::LerpAxis(float CurrentAngle, float DeltaTime) +{ + float TargetAngle = 0.0f; + float FinalReturnSpeed = LeverReturnSpeed; + + switch (LeverReturnTypeWhenReleased) + { + case EVRInteractibleLeverReturnType::LerpToMax: + { + if (CurrentAngle >= 0) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + }break; + case EVRInteractibleLeverReturnType::LerpToMaxIfOverThreshold: + { + if ((!FMath::IsNearlyZero(LeverLimitPositive) && CurrentAngle >= (LeverLimitPositive * LeverTogglePercentage))) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else if ((!FMath::IsNearlyZero(LeverLimitNegative) && CurrentAngle <= -(LeverLimitNegative * LeverTogglePercentage))) + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + }break; + case EVRInteractibleLeverReturnType::RetainMomentum: + { + if (FMath::IsNearlyZero(MomentumAtDrop * DeltaTime, 0.1f)) + { + MomentumAtDrop = 0.0f; + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + return; + } + else + { + MomentumAtDrop = FMath::FInterpTo(MomentumAtDrop, 0.0f, DeltaTime, LeverMomentumFriction); + + FinalReturnSpeed = FMath::Abs(MomentumAtDrop); + + if (MomentumAtDrop >= 0.0f) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + } + + }break; + case EVRInteractibleLeverReturnType::ReturnToZero: + default: + {}break; + } + + //float LerpedVal = FMath::FixedTurn(CurrentAngle, TargetAngle, FinalReturnSpeed * DeltaTime); + float LerpedVal = FMath::FInterpConstantTo(CurrentAngle, TargetAngle, DeltaTime, FinalReturnSpeed); + + if (FMath::IsNearlyEqual(LerpedVal, TargetAngle)) + { + if (LeverRestitution > 0.0f) + { + MomentumAtDrop = -(MomentumAtDrop * LeverRestitution); + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, TargetAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } + else + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, TargetAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } + } + else + { + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, LerpedVal, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } +} + +float UVRLeverComponent::CalcAngle(EVRInteractibleLeverAxis AxisToCalc, FVector CurInteractorLocation, bool bSkipLimits) +{ + float ReturnAxis = 0.0f; + + ReturnAxis = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)AxisToCalc, CurInteractorLocation, InitialGripRot); + + if (bSkipLimits) + return ReturnAxis; + + if (LeverLimitPositive > 0.0f && LeverLimitNegative > 0.0f && FMath::IsNearlyEqual(LeverLimitNegative, 180.f, 0.01f) && FMath::IsNearlyEqual(LeverLimitPositive, 180.f, 0.01f)) + { + // Don't run the clamping or the flip detection, we are a 360 degree lever + } + else + { + ReturnAxis = FMath::ClampAngle(FRotator::NormalizeAxis(RotAtGrab + ReturnAxis), -LeverLimitNegative, LeverLimitPositive); + + // Ignore rotations that would flip the angle of the lever to the other side, with a 90 degree allowance + if (!bIsInFirstTick && ((LeverLimitPositive > 0.0f && LastDeltaAngle >= LeverLimitPositive) || (LeverLimitNegative > 0.0f && LastDeltaAngle <= -LeverLimitNegative)) && FMath::Sign(LastDeltaAngle) != FMath::Sign(ReturnAxis)) + { + ReturnAxis = LastDeltaAngle; + } + } + + bIsInFirstTick = false; + return ReturnAxis; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp new file mode 100644 index 0000000..63ad99b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp @@ -0,0 +1,584 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRMountComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRMountComponent) + +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +//#include "PhysicsPublic.h" +//#include "PhysicsEngine/ConstraintInstance.h" +#include "Net/UnrealNetwork.h" + +//============================================================================= +UVRMountComponent::UVRMountComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + Stiffness = 1500.0f; + Damping = 200.0f; + + MountRotationAxis = EVRInteractibleMountAxis::Axis_XZ; + + + InitialRelativeTransform = FTransform::Identity; + InitialInteractorLocation = FVector::ZeroVector; + InitialGripRot = 0.0f; + qRotAtGrab = FQuat::Identity; + + bDenyGripping = false; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + FlipingZone = 0.4; + FlipReajustYawSpeed = 7.7; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRMountComponent::~UVRMountComponent() +{ +} + + +void UVRMountComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRMountComponent, bRepGameplayTags); + DOREPLIFETIME(UVRMountComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRMountComponent, GameplayTags, COND_Custom); +} + +void UVRMountComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UVRMountComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRMountComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialMountLocation(); // Load the original mount location +} + +void UVRMountComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); +} + +void UVRMountComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + +} + +void UVRMountComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void UVRMountComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + // Handle manual tracking here + + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + switch (MountRotationAxis) + { + case EVRInteractibleMountAxis::Axis_XZ: + { + //The current Mount to target location vector. + FVector MountToTarget; + + //In case the Mount is initially gripped on the back (negative of relative x axis) + if (GrippedOnBack) + { + CurInteractorLocation = CurInteractorLocation *-1; + } + + //Rotate the Initial Grip relative to the Forward Axis so it represents the current correct vector after Mount is rotated. + FRotator RelativeRot = GetRelativeRotation(); + FVector CurToForwardAxisVec = FRotator(RelativeRot.Pitch, RelativeRot.Yaw, TwistDiff).RotateVector(InitialGripToForwardVec); + + //The Current InteractorLocation based on current interactor location to intersection point on sphere with forward axis. + CurInteractorLocation = (CurInteractorLocation.GetSafeNormal() * InitialInteractorLocation.Size() + CurToForwardAxisVec).GetSafeNormal()*CurInteractorLocation.Size(); + + FRotator Rot; + + //If we are inside the fliping zone defined from Cone Area on top or bottom. ToDo: This probably can be combined with later FlipZone. + if (bIsInsideFrontFlipingZone || bIsInsideBackFlipZone) + { + // Entering the flip zone for the first time, a initial forward plane is made to ajust currentinteractorlocation relative x and y. This immitates a mechanical pull/push + if (!bFirstEntryToHalfFlipZone) + { + //Unmodified Right vector can still be used here to create the plane, before it will be changed later. + ForwardPullPlane = FPlane(FVector::ZeroVector, FVector(EntryRightVec.X, EntryRightVec.Y, 0).GetSafeNormal()); + + bFirstEntryToHalfFlipZone = true; + bLerpingOutOfFlipZone = false; + + CurInterpGripLoc = CurInteractorLocation; + + CurPointOnForwardPlane = FPlane::PointPlaneProject(CurInteractorLocation, ForwardPullPlane); + } + + LastPointOnForwardPlane = CurPointOnForwardPlane; + + CurPointOnForwardPlane = FPlane::PointPlaneProject(CurInteractorLocation, ForwardPullPlane); + + FVector ForwardPlainDiffVec = CurPointOnForwardPlane - LastPointOnForwardPlane; + + //Add the difference to how much we moved trough the forward plane since the last frame. + CurInterpGripLoc += ForwardPlainDiffVec; + + // InterpTo the current projected point on forward plane over time when inside the FlipZone. + CurInterpGripLoc = FMath::VInterpConstantTo(CurInterpGripLoc, CurPointOnForwardPlane, GetWorld()->GetDeltaSeconds(), 50); + + //The current location of the motion controller projected on to the forward plane. Only x and y is used from interpolation. + MountToTarget = FVector(CurInterpGripLoc.X, CurInterpGripLoc.Y, CurInteractorLocation.Z).GetSafeNormal(); + + //Save the CurInteractorLocation once to modify roll with it later + CurInteractorLocation = FVector(CurInterpGripLoc.X, CurInterpGripLoc.Y, CurInteractorLocation.Z); + } + else + { + //When going out of whole FlipZone Lerp yaw back to curinteractorlocation in case relative entry xy deviates to much from when leaving area + if (bLerpingOutOfFlipZone) + { + //If projected point on plane is not close enough to the "real" motion controller location lerp + if ((CurInterpGripLoc - CurInteractorLocation).Size() >= 1.0f) + { + //How fast yaw should rotate back when leaving flipzone. ToDo: For LerpOutSpeed maybe better us distance deviation + LerpOutAlpha = FMath::Clamp(LerpOutAlpha + FlipReajustYawSpeed / 100.0f, 0.0f, 1.0f); + + //Lerp + CurInterpGripLoc = CurInterpGripLoc + LerpOutAlpha * (CurInteractorLocation - CurInterpGripLoc); + + //The new vector to make rotation from. + MountToTarget = CurInterpGripLoc.GetSafeNormal(); + + //We left the flipzone completly, set everything back to normal. + if (LerpOutAlpha >= 0.97f) + { + bLerpingOutOfFlipZone = false; + bIsInsideBackFlipZone = false; + } + } + else + { + //If we are already near the real controller location just snap back + bLerpingOutOfFlipZone = false; + bIsInsideBackFlipZone = false; + MountToTarget = CurInteractorLocation.GetSafeNormal(); + } + } + else + { + //There is still a possibility here maybe to be inside backflipzone, but Mount is actually already outside. So just use unmodified rotation. + MountToTarget = CurInteractorLocation.GetSafeNormal(); + bIsInsideBackFlipZone = false; + } + + } + + //Setting the relative rotation once before using "accumulated" relative rotation to calculate roll onto it. + Rot = MountToTarget.Rotation(); + this->SetRelativeRotation((FTransform(FRotator(Rot.Pitch, Rot.Yaw, 0))*InitialRelativeTransform).Rotator()); + + + FVector nAxis; + float FlipAngle; + FQuat::FindBetweenVectors(FVector(0, 0, 1), CurInteractorLocation.GetSafeNormal()).ToAxisAndAngle(nAxis, FlipAngle); + + // This part takes care of the roll rotation ff Mount is inside flipping zone on top or on bottom. + if (FlipAngle < FlipingZone || FlipAngle > PI - FlipingZone) + { + //When entering FlipZone for the first time setup initial properties + if (!bIsInsideFrontFlipingZone && !bIsInsideBackFlipZone) + { + //We entered the FrontFlipzone + bIsInsideFrontFlipingZone = true; + + //Up and Right Vector when entering the FlipZone. Parent Rotation is accounted for. + if (USceneComponent * ParentComp = GetAttachParent()) + { + EntryUpVec = ParentComp->GetComponentRotation().UnrotateVector(GetUpVector()); + EntryRightVec = ParentComp->GetComponentRotation().UnrotateVector(GetRightVector()); + } + else + { + EntryUpVec = GetUpVector(); + EntryRightVec = GetRightVector(); + } + + //Only relative x and y is important for a Mount which has XY rotation limitation for the flip plane. + EntryUpXYNeg = FVector(EntryUpVec.X, EntryUpVec.Y, 0).GetSafeNormal()*-1; + //If flipping over the bottom + if (FlipAngle > PI - FlipingZone) + { + EntryUpXYNeg *= -1; + } + + //A Plane perpendicular to the FlipZone relative EntryPoint XY UpVector. This plane determines when the roll has to be turned by 180 degree + FlipPlane = FPlane(FVector::ZeroVector, EntryUpXYNeg); + } + + //CurInteractor vector to its projected point on flipplane + FVector CurInteractorToFlipPlaneVec = CurInteractorLocation - FPlane::PointPlaneProject(CurInteractorLocation, FlipPlane); + + if (bIsInsideFrontFlipingZone) + { + //If Mount rotation is on or over flipplane but still inside frontflipzone flip the roll. + if (FVector::DotProduct(CurInteractorToFlipPlaneVec, EntryUpXYNeg) <= 0) + { + bIsInsideFrontFlipingZone = false; + bIsInsideBackFlipZone = true; + + bIsFlipped = !bIsFlipped; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + } + else + { + //If Mount Rotation is still inside FrontFlipZone ajust the roll so it looks naturally when moving the Mount against the FlipPlane + + FVector RelativeUpVec = GetUpVector(); + + if(USceneComponent * ParentComp = GetAttachParent()) + RelativeUpVec = ParentComp->GetComponentRotation().UnrotateVector(RelativeUpVec); + + FVector CurrentUpVec = FVector(RelativeUpVec.X, RelativeUpVec.Y, 0).GetSafeNormal(); + + //If rotating over the top ajust relative UpVector + if (FlipAngle < FlipingZone) + { + CurrentUpVec *= -1; + } + + float EntryTwist = FMath::Atan2(EntryUpXYNeg.Y, EntryUpXYNeg.X); + float CurTwist = FMath::Atan2(CurrentUpVec.Y, CurrentUpVec.X); + + //Rotate the roll so relative up vector x y looks at the flip plane + if (bIsFlipped) + { + TwistDiff = FMath::RadiansToDegrees(EntryTwist - CurTwist - PI); + } + else + { + TwistDiff = FMath::RadiansToDegrees(EntryTwist - CurTwist); + } + } + } + else + { + //If Inside Back Flip Zone just flip the roll. ToDo: Dont just ajust roll to 0 or 180. Calculate Twist diff according to up vector to FlipPlane. + if (bIsInsideBackFlipZone) + { + if (FVector::DotProduct(CurInteractorToFlipPlaneVec, EntryUpXYNeg) >= 0) + { + bIsInsideFrontFlipingZone = true; + bIsInsideBackFlipZone = false; + + bIsFlipped = !bIsFlipped; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + } + } + } + + } + else + { + //If the Mount went into the flipping zone and back out without going over flip plane reset roll + bIsInsideFrontFlipingZone = false; + bIsInsideBackFlipZone = false; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + + if (bFirstEntryToHalfFlipZone) + { + //If never left FlipZone before but doing now it now, reset first time entry bool. ToDo: Maybe better to do this elsewhere. + bFirstEntryToHalfFlipZone = false; + LerpOutAlpha = 0; + + //We left the flipzone so rotate yaw back from interpolated controller position on forward pull plane back to "real" one. + bLerpingOutOfFlipZone = true; + } + + } + + //Add Roll modifications to accumulated LocalRotation. + this->AddLocalRotation(FRotator(0, 0, -TwistDiff)); + + }break; + default:break; + } + + // #TODO: This drop code is incorrect, it is based off of the initial point and not the location at grip - revise it at some point + // Also set it to after rotation + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialInteractorDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return; + } +} + +void UVRMountComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform RelativeToGripTransform = ReversedRelativeTransform * this->GetComponentTransform(); + + //continue here CurToForwardAxis is based on last gripped location ---> change this + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialInteractorDropLocation = ReversedRelativeTransform.GetTranslation(); + + switch (MountRotationAxis) + { + case EVRInteractibleMountAxis::Axis_XZ: + { + //spaceharry + + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + + FVector ForwardVectorToUse = GetForwardVector(); + + if (USceneComponent * ParentComp = GetAttachParent()) + { + ForwardVectorToUse = ParentComp->GetComponentRotation().UnrotateVector(ForwardVectorToUse); + } + + InitialForwardVector = InitialInteractorLocation.Size() * ForwardVectorToUse; + + if (FVector::DotProduct(InitialInteractorLocation, ForwardVectorToUse) <= 0) + { + GrippedOnBack = true; + InitialGripToForwardVec = (InitialForwardVector + InitialInteractorLocation); + } + else + { + InitialGripToForwardVec = InitialForwardVector - InitialInteractorLocation; + GrippedOnBack = false; + } + + FRotator RelativeRot = GetRelativeRotation(); + InitialGripToForwardVec = FRotator(RelativeRot.Pitch, RelativeRot.Yaw, TwistDiff).UnrotateVector(InitialGripToForwardVec); + + }break; + default:break; + } + + + + + this->SetComponentTickEnabled(true); +} + +void UVRMountComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + this->SetComponentTickEnabled(false); +} + +void UVRMountComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRMountComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRMountComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnUsed_Implementation() {} +void UVRMountComponent::OnEndUsed_Implementation() {} +void UVRMountComponent::OnSecondaryUsed_Implementation() {} +void UVRMountComponent::OnEndSecondaryUsed_Implementation() {} +void UVRMountComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRMountComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRMountComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRMountComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRMountComponent::SimulateOnDrop_Implementation() +{ + return false; +} + + +EGripCollisionType UVRMountComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRMountComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRMountComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRMountComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRMountComponent::GripStiffness_Implementation() +{ +return Stiffness; +} + +float UVRMountComponent::GripDamping_Implementation() +{ +return Damping; +}*/ +void UVRMountComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = Stiffness; + GripDampingOut = Damping; +} + +FBPAdvGripSettings UVRMountComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRMountComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRMountComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ +bHadSlotInRange = false; +} + +void UVRMountComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ +bHadSlotInRange = false; +}*/ + +void UVRMountComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRMountComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRMountComponent::IsHeld_Implementation(TArray & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRMountComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRMountComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + + +bool UVRMountComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + return false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp new file mode 100644 index 0000000..7f7f860 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp @@ -0,0 +1,944 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRSliderComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRSliderComponent) + +#include "VRExpansionFunctionLibrary.h" +#include "Components/SplineComponent.h" +#include "GripMotionControllerComponent.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRSliderComponent::UVRSliderComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + + InitialRelativeTransform = FTransform::Identity; + bDenyGripping = false; + + bUpdateInTick = false; + bPassThrough = false; + + MinSlideDistance = FVector::ZeroVector; + MaxSlideDistance = FVector(10.0f, 0.f, 0.f); + SliderRestitution = 0.0f; + CurrentSliderProgress = 0.0f; + LastSliderProgress = FVector::ZeroVector;//0.0f; + SplineLastSliderProgress = 0.0f; + + MomentumAtDrop = FVector::ZeroVector;// 0.0f; + SplineMomentumAtDrop = 0.0f; + SliderMomentumFriction = 3.0f; + MaxSliderMomentum = 1.0f; + FramesToAverage = 3; + + InitialInteractorLocation = FVector::ZeroVector; + InitialGripLoc = FVector::ZeroVector; + + bSlideDistanceIsInParentSpace = true; + bUseLegacyLogic = false; + bIsLocked = false; + bAutoDropWhenLocked = true; + + SplineComponentToFollow = nullptr; + + bFollowSplineRotationAndScale = false; + SplineLerpType = EVRInteractibleSliderLerpType::Lerp_None; + SplineLerpValue = 8.f; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + LastSliderProgressState = -1.0f; + LastInputKey = 0.0f; + + bSliderUsesSnapPoints = false; + SnapIncrement = 0.1f; + SnapThreshold = 0.1f; + bIncrementProgressBetweenSnapPoints = false; + EventThrowThreshold = 1.0f; + bHitEventThreshold = false; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRSliderComponent::~UVRSliderComponent() +{ +} + + +void UVRSliderComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRSliderComponent, InitialRelativeTransform); + DOREPLIFETIME(UVRSliderComponent, SplineComponentToFollow); + //DOREPLIFETIME_CONDITION(UVRSliderComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRSliderComponent, bRepGameplayTags); + DOREPLIFETIME(UVRSliderComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRSliderComponent, GameplayTags, COND_Custom); +} + +void UVRSliderComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRSliderComponent, InitialRelativeTransform, bReplicateMovement); + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRSliderComponent, SplineComponentToFollow, bReplicateMovement); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(UVRSliderComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRSliderComponent::OnRegister() +{ + Super::OnRegister(); + + // Init the slider settings + if (USplineComponent * ParentSpline = Cast(GetAttachParent())) + { + SetSplineComponentToFollow(ParentSpline); + } + else + { + ResetInitialSliderLocation(); + } +} + +void UVRSliderComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + CalculateSliderProgress(); + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRSliderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (bIsHeld && bUpdateInTick && HoldingGrip.HoldingController) + { + FBPActorGripInformation GripInfo; + EBPVRResultSwitch Result; + HoldingGrip.HoldingController->GetGripByID(GripInfo, HoldingGrip.GripID, Result); + + if (Result == EBPVRResultSwitch::OnSucceeded) + { + bPassThrough = true; + TickGrip_Implementation(HoldingGrip.HoldingController, GripInfo, DeltaTime); + bPassThrough = false; + } + return; + } + + // If we are locked then end the lerp, no point + if (bIsLocked) + { + // Notify the end user + OnSliderFinishedLerping.Broadcast(CurrentSliderProgress); + ReceiveSliderFinishedLerping(CurrentSliderProgress); + + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + + return; + } + + if (bIsLerping) + { + if ((SplineComponentToFollow && FMath::IsNearlyZero((SplineMomentumAtDrop * DeltaTime), 0.00001f)) || (!SplineComponentToFollow && (MomentumAtDrop * DeltaTime).IsNearlyZero(0.00001f))) + { + bIsLerping = false; + } + else + { + if (this->SplineComponentToFollow) + { + SplineMomentumAtDrop = FMath::FInterpTo(SplineMomentumAtDrop, 0.0f, DeltaTime, SliderMomentumFriction); + float newProgress = CurrentSliderProgress + (SplineMomentumAtDrop * DeltaTime); + + if (newProgress < 0.0f || FMath::IsNearlyEqual(newProgress, 0.0f, 0.00001f)) + { + if (SliderRestitution > 0.0f) + { + // Reverse the momentum + SplineMomentumAtDrop = -(SplineMomentumAtDrop * SliderRestitution); + this->SetSliderProgress(0.0f); + } + else + { + bIsLerping = false; + this->SetSliderProgress(0.0f); + } + } + else if (newProgress > 1.0f || FMath::IsNearlyEqual(newProgress, 1.0f, 0.00001f)) + { + if (SliderRestitution > 0.0f) + { + // Reverse the momentum + SplineMomentumAtDrop = -(SplineMomentumAtDrop * SliderRestitution); + this->SetSliderProgress(1.0f); + } + else + { + bIsLerping = false; + this->SetSliderProgress(1.0f); + } + } + else + { + this->SetSliderProgress(newProgress); + } + } + else + { + MomentumAtDrop = FMath::VInterpTo(MomentumAtDrop, FVector::ZeroVector, DeltaTime, SliderMomentumFriction); + + FVector ClampedLocation = ClampSlideVector(InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()) + (MomentumAtDrop * DeltaTime)); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? ClampedLocation * InitialRelativeTransform.GetScale3D() : ClampedLocation); + float newProgress = CurrentSliderProgress; + if (SliderRestitution > 0.0f) + { + // Implement bounce + FVector CurLoc = ClampedLocation; + + if ( + (FMath::Abs(MinSlideDistance.X) > 0.0f && CurLoc.X <= -FMath::Abs(this->MinSlideDistance.X)) || + (FMath::Abs(MinSlideDistance.Y) > 0.0f && CurLoc.Y <= -FMath::Abs(this->MinSlideDistance.Y)) || + (FMath::Abs(MinSlideDistance.Z) > 0.0f && CurLoc.Z <= -FMath::Abs(this->MinSlideDistance.Z)) || + (FMath::Abs(MaxSlideDistance.X) > 0.0f && CurLoc.X >= FMath::Abs(this->MaxSlideDistance.X)) || + (FMath::Abs(MaxSlideDistance.Y) > 0.0f && CurLoc.Y >= FMath::Abs(this->MaxSlideDistance.Y)) || + (FMath::Abs(MaxSlideDistance.Z) > 0.0f && CurLoc.Z >= FMath::Abs(this->MaxSlideDistance.Z)) + ) + { + MomentumAtDrop = (-MomentumAtDrop * SliderRestitution); + } + } + else + { + if (newProgress < 0.0f || FMath::IsNearlyEqual(newProgress, 0.0f, 0.00001f)) + { + bIsLerping = false; + this->SetSliderProgress(0.0f); + } + else if (newProgress > 1.0f || FMath::IsNearlyEqual(newProgress, 1.0f, 0.00001f)) + { + bIsLerping = false; + this->SetSliderProgress(1.0f); + } + } + } + } + + if (!bIsLerping) + { + // Notify the end user + OnSliderFinishedLerping.Broadcast(CurrentSliderProgress); + ReceiveSliderFinishedLerping(CurrentSliderProgress); + + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + // Check for the hit point always + CheckSliderProgress(); + } +} + +void UVRSliderComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + + // Skip this tick if its not triggered from the pass through + if (bUpdateInTick && !bPassThrough) + return; + + // If the sliders progress is locked then just exit early + if (bIsLocked) + { + if (bAutoDropWhenLocked) + { + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); + } + + return; + } + + // Handle manual tracking here + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + FVector CalculatedLocation = InitialGripLoc + (CurInteractorLocation - InitialInteractorLocation); + + float SplineProgress = CurrentSliderProgress; + if (SplineComponentToFollow != nullptr) + { + FVector WorldCalculatedLocation = CurrentRelativeTransform.TransformPosition(CalculatedLocation); + float ClosestKey = SplineComponentToFollow->FindInputKeyClosestToWorldLocation(WorldCalculatedLocation); + + if (bSliderUsesSnapPoints) + { + float SplineLength = SplineComponentToFollow->GetSplineLength(); + SplineProgress = GetCurrentSliderProgress(WorldCalculatedLocation, true, ClosestKey); + + SplineProgress = UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(SplineProgress, SnapIncrement, SnapThreshold); + + const int32 NumPoints = SplineComponentToFollow->SplineCurves.Position.Points.Num(); + + if (SplineComponentToFollow->SplineCurves.Position.Points.Num() > 1) + { + ClosestKey = SplineComponentToFollow->SplineCurves.ReparamTable.Eval(SplineProgress * SplineLength, 0.0f); + } + + WorldCalculatedLocation = SplineComponentToFollow->GetLocationAtSplineInputKey(ClosestKey, ESplineCoordinateSpace::World); + } + + bool bLerpToNewKey = true; + bool bChangedLocation = false; + + if (bEnforceSplineLinearity && LastInputKey >= 0.0f && + FMath::Abs((FMath::TruncToFloat(ClosestKey) - FMath::TruncToFloat(LastInputKey))) > 1.0f && + (!bSliderUsesSnapPoints || (SplineProgress - CurrentSliderProgress > SnapIncrement)) + ) + { + bLerpToNewKey = false; + } + else + { + LerpedKey = ClosestKey; + } + + if (bFollowSplineRotationAndScale) + { + FTransform trans; + if (SplineLerpType != EVRInteractibleSliderLerpType::Lerp_None && LastInputKey >= 0.0f && !FMath::IsNearlyEqual(LerpedKey, LastInputKey)) + { + GetLerpedKey(LerpedKey, DeltaTime); + trans = SplineComponentToFollow->GetTransformAtSplineInputKey(LerpedKey, ESplineCoordinateSpace::World, true); + bChangedLocation = true; + } + else if (bLerpToNewKey) + { + trans = SplineComponentToFollow->FindTransformClosestToWorldLocation(WorldCalculatedLocation, ESplineCoordinateSpace::World, true); + bChangedLocation = true; + } + + if (bChangedLocation) + { + trans.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + trans = trans * ParentTransform.Inverse(); + this->SetRelativeTransform(trans); + } + } + else + { + FVector WorldLocation; + if (SplineLerpType != EVRInteractibleSliderLerpType::Lerp_None && LastInputKey >= 0.0f && !FMath::IsNearlyEqual(LerpedKey, LastInputKey)) + { + GetLerpedKey(LerpedKey, DeltaTime); + WorldLocation = SplineComponentToFollow->GetLocationAtSplineInputKey(LerpedKey, ESplineCoordinateSpace::World); + bChangedLocation = true; + } + else if (bLerpToNewKey) + { + WorldLocation = SplineComponentToFollow->FindLocationClosestToWorldLocation(WorldCalculatedLocation, ESplineCoordinateSpace::World); + bChangedLocation = true; + } + + if (bChangedLocation) + this->SetRelativeLocation(ParentTransform.InverseTransformPosition(WorldLocation)); + } + + CurrentSliderProgress = GetCurrentSliderProgress(WorldCalculatedLocation, true, bLerpToNewKey ? LerpedKey : ClosestKey); + if (bLerpToNewKey) + { + LastInputKey = LerpedKey; + } + } + else + { + FVector ClampedLocation = ClampSlideVector(CalculatedLocation); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? ClampedLocation * InitialRelativeTransform.GetScale3D() : ClampedLocation); + } + + if (SliderBehaviorWhenReleased == EVRInteractibleSliderDropBehavior::RetainMomentum) + { + if (SplineComponentToFollow) + { + // Rolling average across num samples + SplineMomentumAtDrop -= SplineMomentumAtDrop / FramesToAverage; + SplineMomentumAtDrop += ((CurrentSliderProgress - SplineLastSliderProgress) / DeltaTime) / FramesToAverage; + + float momentumSign = FMath::Sign(SplineMomentumAtDrop); + SplineMomentumAtDrop = momentumSign * FMath::Min(MaxSliderMomentum, FMath::Abs(SplineMomentumAtDrop)); + + SplineLastSliderProgress = CurrentSliderProgress; + } + else + { + // Rolling average across num samples + MomentumAtDrop -= MomentumAtDrop / FramesToAverage; + FVector CurProgress = InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()); + if (bSlideDistanceIsInParentSpace) + CurProgress *= FVector(1.0f) / InitialRelativeTransform.GetScale3D(); + + MomentumAtDrop += ((/*CurrentSliderProgress*/CurProgress - LastSliderProgress) / DeltaTime) / FramesToAverage; + + //MomentumAtDrop = FMath::Min(MaxSliderMomentum, MomentumAtDrop); + + LastSliderProgress = CurProgress;//CurrentSliderProgress; + } + } + + CheckSliderProgress(); + + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); +} + +bool UVRSliderComponent::CheckAutoDrop(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + // Converted to a relative value now so it should be correct + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return true; + } + + return false; +} + +void UVRSliderComponent::CheckSliderProgress() +{ + // Skip first check, this will skip an event throw on rounded + if (LastSliderProgressState < 0.0f) + { + // Skip first tick, this is our resting position + if (!bSliderUsesSnapPoints) + LastSliderProgressState = FMath::RoundToFloat(CurrentSliderProgress); // Ensure it is rounded to 0 or 1 + else + LastSliderProgressState = CurrentSliderProgress; + } + else if ((LastSliderProgressState != CurrentSliderProgress) || bHitEventThreshold) + { + float ModValue = FMath::Fmod(CurrentSliderProgress, SnapIncrement); + if ((!bSliderUsesSnapPoints && (CurrentSliderProgress == 1.0f || CurrentSliderProgress == 0.0f)) || + (bSliderUsesSnapPoints && SnapIncrement > 0.f && (FMath::IsNearlyEqual(ModValue, 0.0f, 0.001f) || FMath::IsNearlyEqual(ModValue, SnapIncrement, 0.00001f))) + ) + { + // I am working with exacts here because of the clamping, it should actually work with no precision issues + // I wanted to ABS(Last-Cur) == 1.0 but it would cause an initial miss on whatever one last was inited to. + + if (!bSliderUsesSnapPoints) + LastSliderProgressState = FMath::RoundToFloat(CurrentSliderProgress); // Ensure it is rounded to 0 or 1 + else + LastSliderProgressState = CurrentSliderProgress; + + ReceiveSliderHitPoint(LastSliderProgressState); + OnSliderHitPoint.Broadcast(LastSliderProgressState); + bHitEventThreshold = false; + } + } + + if (FMath::Abs(LastSliderProgressState - CurrentSliderProgress) >= (bSliderUsesSnapPoints ? FMath::Min(EventThrowThreshold, SnapIncrement / 2.0f) : EventThrowThreshold)) + { + bHitEventThreshold = true; + } +} + +void UVRSliderComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform RelativeToGripTransform = ReversedRelativeTransform * this->GetComponentTransform(); + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialGripLoc = InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()); + InitialDropLocation = ReversedRelativeTransform.GetTranslation(); + LastInputKey = -1.0f; + LerpedKey = 0.0f; + bHitEventThreshold = false; + //LastSliderProgressState = -1.0f; + LastSliderProgress = InitialGripLoc;//CurrentSliderProgress; + SplineLastSliderProgress = CurrentSliderProgress; + + bIsLerping = false; + MomentumAtDrop = FVector::ZeroVector;//0.0f; + SplineMomentumAtDrop = 0.0f; + + if (GripInformation.GripMovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = false; + } + + if (bUpdateInTick) + SetComponentTickEnabled(true); + + //OnGripped.Broadcast(GrippingController, GripInformation); + +} + +void UVRSliderComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + //this->SetComponentTickEnabled(false); + // #TODO: Handle letting go and how lerping works, specifically with the snap points it may be an issue + if (SliderBehaviorWhenReleased != EVRInteractibleSliderDropBehavior::Stay) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + + FVector Len = (MinSlideDistance.GetAbs() + MaxSlideDistance.GetAbs()); + if(bSlideDistanceIsInParentSpace) + Len *= (FVector(1.0f) / InitialRelativeTransform.GetScale3D()); + + float TotalDistance = Len.Size(); + + if (!SplineComponentToFollow) + { + if (MaxSliderMomentum * TotalDistance < MomentumAtDrop.Size()) + { + MomentumAtDrop = MomentumAtDrop.GetSafeNormal() * (TotalDistance * MaxSliderMomentum); + } + } + + if(MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + bReplicateMovement = false; + } + else + { + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRSliderComponent::SetIsLocked(bool bNewLockedState) +{ + bIsLocked = bNewLockedState; +} + +void UVRSliderComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRSliderComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRSliderComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnUsed_Implementation() {} +void UVRSliderComponent::OnEndUsed_Implementation() {} +void UVRSliderComponent::OnSecondaryUsed_Implementation() {} +void UVRSliderComponent::OnEndSecondaryUsed_Implementation() {} +void UVRSliderComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRSliderComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRSliderComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRSliderComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRSliderComponent::SimulateOnDrop_Implementation() +{ + return false; +} + +/*EGripCollisionType UVRSliderComponent::SlotGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +} + +EGripCollisionType UVRSliderComponent::FreeGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +}*/ + +EGripCollisionType UVRSliderComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRSliderComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRSliderComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRSliderComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRSliderComponent::GripStiffness_Implementation() +{ + return 0.0f; +} + +float UVRSliderComponent::GripDamping_Implementation() +{ + return 0.0f; +}*/ +void UVRSliderComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = 0.0f; + GripDampingOut = 0.0f; +} + +FBPAdvGripSettings UVRSliderComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRSliderComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRSliderComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRSliderComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRSliderComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRSliderComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRSliderComponent::IsHeld_Implementation(TArray & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRSliderComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRSliderComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld && !bIsLerping) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRSliderComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRSliderComponent::GetGripScripts_Implementation(TArray & ArrayReference) +{ + return false; +} + +FVector UVRSliderComponent::ClampSlideVector(FVector ValueToClamp) +{ + FVector fScaleFactor = FVector(1.0f); + + if (bSlideDistanceIsInParentSpace) + fScaleFactor = fScaleFactor / InitialRelativeTransform.GetScale3D(); + + FVector MinScale = (bUseLegacyLogic ? MinSlideDistance : MinSlideDistance.GetAbs()) * fScaleFactor; + + FVector Dist = (bUseLegacyLogic ? (MinSlideDistance + MaxSlideDistance) : (MinSlideDistance.GetAbs() + MaxSlideDistance.GetAbs())) * fScaleFactor; + FVector Progress = (ValueToClamp - (-MinScale)) / Dist; + + if (bSliderUsesSnapPoints) + { + Progress.X = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.X, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + Progress.Y = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.Y, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + Progress.Z = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.Z, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + } + else + { + Progress.X = FMath::Clamp(Progress.X, 0.f, 1.f); + Progress.Y = FMath::Clamp(Progress.Y, 0.f, 1.f); + Progress.Z = FMath::Clamp(Progress.Z, 0.f, 1.f); + } + + return (Progress * Dist) - (MinScale); +} + +float UVRSliderComponent::GetDistanceAlongSplineAtSplineInputKey(float InKey) const +{ + + const int32 NumPoints = SplineComponentToFollow->SplineCurves.Position.Points.Num(); + const int32 NumSegments = SplineComponentToFollow->IsClosedLoop() ? NumPoints : NumPoints - 1; + + if ((InKey >= 0) && (InKey < NumSegments)) + { + const int32 ReparamPrevIndex = static_cast(InKey * SplineComponentToFollow->ReparamStepsPerSegment); + const int32 ReparamNextIndex = ReparamPrevIndex + 1; + + const float Alpha = (InKey * SplineComponentToFollow->ReparamStepsPerSegment) - static_cast(ReparamPrevIndex); + + const float PrevDistance = SplineComponentToFollow->SplineCurves.ReparamTable.Points[ReparamPrevIndex].InVal; + const float NextDistance = SplineComponentToFollow->SplineCurves.ReparamTable.Points[ReparamNextIndex].InVal; + + // ReparamTable assumes that distance and input keys have a linear relationship in-between entries. + return FMath::Lerp(PrevDistance, NextDistance, Alpha); + } + else if (InKey >= NumSegments) + { + return SplineComponentToFollow->SplineCurves.GetSplineLength(); + } + + return 0.0f; +} + +float UVRSliderComponent::GetCurrentSliderProgress(FVector CurLocation, bool bUseKeyInstead, float CurKey) +{ + if (SplineComponentToFollow != nullptr) + { + // In this case it is a world location + float ClosestKey = CurKey; + + if (!bUseKeyInstead) + ClosestKey = SplineComponentToFollow->FindInputKeyClosestToWorldLocation(CurLocation); + + /*int32 primaryKey = FMath::TruncToInt(ClosestKey); + + float distance1 = SplineComponentToFollow->GetDistanceAlongSplineAtSplinePoint(primaryKey); + float distance2 = SplineComponentToFollow->GetDistanceAlongSplineAtSplinePoint(primaryKey + 1); + + float FinalDistance = ((distance2 - distance1) * (ClosestKey - (float)primaryKey)) + distance1; + return FMath::Clamp(FinalDistance / SplineComponentToFollow->GetSplineLength(), 0.0f, 1.0f);*/ + float SplineLength = SplineComponentToFollow->GetSplineLength(); + return GetDistanceAlongSplineAtSplineInputKey(ClosestKey) / SplineLength; + } + + // Should need the clamp normally, but if someone is manually setting locations it could go out of bounds + float Progress = 0.f; + + if (bUseLegacyLogic) + { + Progress = FMath::Clamp(FVector::Dist(-MinSlideDistance, CurLocation) / FVector::Dist(-MinSlideDistance, MaxSlideDistance), 0.0f, 1.0f); + } + else + { + Progress = FMath::Clamp(FVector::Dist(-MinSlideDistance.GetAbs(), CurLocation) / FVector::Dist(-MinSlideDistance.GetAbs(), MaxSlideDistance.GetAbs()), 0.0f, 1.0f); + } + + if (bSliderUsesSnapPoints && SnapThreshold < SnapIncrement) + { + if (FMath::Fmod(Progress, SnapIncrement) < SnapThreshold) + { + Progress = FMath::GridSnap(Progress, SnapIncrement); + } + else if(!bIncrementProgressBetweenSnapPoints) + { + Progress = CurrentSliderProgress; + } + } + + return Progress; +} + +void UVRSliderComponent::GetLerpedKey(float &ClosestKey, float DeltaTime) +{ + switch (SplineLerpType) + { + case EVRInteractibleSliderLerpType::Lerp_Interp: + { + ClosestKey = FMath::FInterpTo(LastInputKey, ClosestKey, DeltaTime, SplineLerpValue); + }break; + case EVRInteractibleSliderLerpType::Lerp_InterpConstantTo: + { + ClosestKey = FMath::FInterpConstantTo(LastInputKey, ClosestKey, DeltaTime, SplineLerpValue); + }break; + + default: break; + } +} + +void UVRSliderComponent::SetSplineComponentToFollow(USplineComponent * SplineToFollow) +{ + SplineComponentToFollow = SplineToFollow; + + if (SplineToFollow != nullptr) + ResetToParentSplineLocation(); + else + CalculateSliderProgress(); +} + +void UVRSliderComponent::ResetInitialSliderLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + ResetToParentSplineLocation(); + + if (SplineComponentToFollow == nullptr) + CurrentSliderProgress = GetCurrentSliderProgress(FVector(0, 0, 0)); +} + +void UVRSliderComponent::ResetToParentSplineLocation() +{ + if (SplineComponentToFollow != nullptr) + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform WorldTransform = SplineComponentToFollow->FindTransformClosestToWorldLocation(this->GetComponentLocation(), ESplineCoordinateSpace::World, true); + if (bFollowSplineRotationAndScale) + { + WorldTransform.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + WorldTransform = WorldTransform * ParentTransform.Inverse(); + this->SetRelativeTransform(WorldTransform); + } + else + { + this->SetWorldLocation(WorldTransform.GetLocation()); + } + + CurrentSliderProgress = GetCurrentSliderProgress(WorldTransform.GetLocation()); + } +} + +void UVRSliderComponent::SetSliderProgress(float NewSliderProgress) +{ + NewSliderProgress = FMath::Clamp(NewSliderProgress, 0.0f, 1.0f); + + if (SplineComponentToFollow != nullptr) + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + float splineProgress = SplineComponentToFollow->GetSplineLength() * NewSliderProgress; + + if (bFollowSplineRotationAndScale) + { + FTransform trans = SplineComponentToFollow->GetTransformAtDistanceAlongSpline(splineProgress, ESplineCoordinateSpace::World, true); + trans.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + trans = trans * ParentTransform.Inverse(); + this->SetRelativeTransform(trans); + } + else + { + this->SetRelativeLocation(ParentTransform.InverseTransformPosition(SplineComponentToFollow->GetLocationAtDistanceAlongSpline(splineProgress, ESplineCoordinateSpace::World))); + } + } + else // Not a spline follow + { + // Doing it min+max because the clamp value subtracts the min value + FVector CalculatedLocation = bUseLegacyLogic ? FMath::Lerp(-MinSlideDistance, MaxSlideDistance, NewSliderProgress) : FMath::Lerp(-MinSlideDistance.GetAbs(), MaxSlideDistance.GetAbs(), NewSliderProgress); + + if (bSlideDistanceIsInParentSpace) + CalculatedLocation *= FVector(1.0f) / InitialRelativeTransform.GetScale3D(); + + FVector ClampedLocation = ClampSlideVector(CalculatedLocation); + + //if (bSlideDistanceIsInParentSpace) + // this->SetRelativeLocation(InitialRelativeTransform.TransformPositionNoScale(ClampedLocation)); + //else + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + } + + CurrentSliderProgress = NewSliderProgress; +} + +float UVRSliderComponent::CalculateSliderProgress() +{ + if (this->SplineComponentToFollow != nullptr) + { + CurrentSliderProgress = GetCurrentSliderProgress(this->GetComponentLocation()); + } + else + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FVector CalculatedLocation = CurrentRelativeTransform.InverseTransformPosition(this->GetComponentLocation()); + + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? CalculatedLocation * InitialRelativeTransform.GetScale3D() : CalculatedLocation); + } + + return CurrentSliderProgress; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp new file mode 100644 index 0000000..8a20353 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp @@ -0,0 +1,398 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/BucketUpdateSubsystem.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(BucketUpdateSubsystem) + + bool UBucketUpdateSubsystem::AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || UpdateHTZ < 1) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::K2_AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || UpdateHTZ < 1) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, InObject, FunctionName); + } + + + bool UBucketUpdateSubsystem::K2_AddObjectEventToBucket(FDynamicBucketUpdateTickSignature Delegate, int32 UpdateHTZ) + { + if (!Delegate.IsBound()) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, Delegate); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromBucketByFunctionName(UObject* InObject, FName FunctionName) + { + if (!InObject) + return false; + + return BucketContainer.RemoveBucketObject(InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromBucketByEvent(FDynamicBucketUpdateTickSignature Delegate) + { + if (!Delegate.IsBound()) + return false; + + return BucketContainer.RemoveBucketObject(Delegate); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromAllBuckets(UObject* InObject) + { + if (!InObject) + return false; + + return BucketContainer.RemoveObjectFromAllBuckets(InObject); + } + + bool UBucketUpdateSubsystem::IsObjectFunctionInBucket(UObject* InObject, FName FunctionName) + { + if (!InObject) + return false; + + return BucketContainer.IsObjectFunctionInBucket(InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::IsActive() + { + return BucketContainer.bNeedsUpdate; + } + + void UBucketUpdateSubsystem::Tick(float DeltaTime) + { + BucketContainer.UpdateBuckets(DeltaTime); + } + + bool UBucketUpdateSubsystem::IsTickable() const + { + return BucketContainer.bNeedsUpdate; + } + + UWorld* UBucketUpdateSubsystem::GetTickableGameObjectWorld() const + { + return GetWorld(); + } + + bool UBucketUpdateSubsystem::IsTickableInEditor() const + { + return false; + } + + bool UBucketUpdateSubsystem::IsTickableWhenPaused() const + { + return false; + } + + ETickableTickType UBucketUpdateSubsystem::GetTickableTickType() const + { + if (IsTemplate(RF_ClassDefaultObject)) + return ETickableTickType::Never; + + return ETickableTickType::Conditional; + } + + TStatId UBucketUpdateSubsystem::GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(UVRGripScriptBase, STATGROUP_Tickables); + } + + bool FUpdateBucketDrop::ExecuteBoundCallback() + { + if (NativeCallback.IsBound()) + { + return NativeCallback.Execute(); + } + else if (DynamicCallback.IsBound()) + { + DynamicCallback.Execute(); + return true; + } + + return false; + } + + bool FUpdateBucketDrop::IsBoundToObjectFunction(UObject * Obj, FName & FuncName) + { + return (NativeCallback.IsBoundToObject(Obj) && FunctionName == FuncName); + } + + bool FUpdateBucketDrop::IsBoundToObjectDelegate(FDynamicBucketUpdateTickSignature & DynEvent) + { + return DynamicCallback == DynEvent; + } + + bool FUpdateBucketDrop::IsBoundToObject(UObject * Obj) + { + return (NativeCallback.IsBoundToObject(Obj) || DynamicCallback.IsBoundToObject(Obj)); + } + + FUpdateBucketDrop::FUpdateBucketDrop() + { + FunctionName = NAME_None; + } + + FUpdateBucketDrop::FUpdateBucketDrop(FDynamicBucketUpdateTickSignature & DynCallback) + { + DynamicCallback = DynCallback; + } + + FUpdateBucketDrop::FUpdateBucketDrop(UObject * Obj, FName FuncName) + { + if (Obj && Obj->FindFunction(FuncName)) + { + FunctionName = FuncName; + NativeCallback.BindUFunction(Obj, FunctionName); + } + else + { + FunctionName = NAME_None; + } + } + + bool FUpdateBucket::Update(float DeltaTime) + { + //#TODO: Need to consider batching / spreading out load if there are a lot of updating objects in the bucket + if (Callbacks.Num() < 1) + return false; + + // Check for if this bucket is ready to fire events + nUpdateCount += DeltaTime; + if (nUpdateCount >= nUpdateRate) + { + nUpdateCount = 0.0f; + for (int i = Callbacks.Num() - 1; i >= 0; --i) + { + if (Callbacks[i].ExecuteBoundCallback()) + { + // If this returns true then we keep it in the queue + continue; + } + + // Remove the callback, it is complete or invalid + Callbacks.RemoveAt(i); + } + } + + return Callbacks.Num() > 0; + } + + void FUpdateBucketContainer::UpdateBuckets(float DeltaTime) + { + TArray BucketsToRemove; + for(auto& Bucket : ReplicationBuckets) + { + if (!Bucket.Value.Update(DeltaTime)) + { + // Add Bucket to list to remove at end of update + BucketsToRemove.Add(Bucket.Key); + } + } + + // Remove unused buckets so that they don't get ticked + for (const uint32 Key : BucketsToRemove) + { + ReplicationBuckets.Remove(Key); + } + + if (ReplicationBuckets.Num() < 1) + bNeedsUpdate = false; + } + + bool FUpdateBucketContainer::AddBucketObject(uint32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || InObject->FindFunction(FunctionName) == nullptr || UpdateHTZ < 1) + return false; + + // First verify that this object isn't already contained in a bucket, if it is then erase it so that we can replace it below + RemoveBucketObject(InObject, FunctionName); + + if (ReplicationBuckets.Contains(UpdateHTZ)) + { + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(InObject, FunctionName)); + } + else + { + FUpdateBucket & newBucket = ReplicationBuckets.Add(UpdateHTZ, FUpdateBucket(UpdateHTZ)); + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(InObject, FunctionName)); + } + + if (ReplicationBuckets.Num() > 0) + bNeedsUpdate = true; + + return true; + } + + + bool FUpdateBucketContainer::AddBucketObject(uint32 UpdateHTZ, FDynamicBucketUpdateTickSignature &Delegate) + { + if (!Delegate.IsBound() || UpdateHTZ < 1) + return false; + + // First verify that this object isn't already contained in a bucket, if it is then erase it so that we can replace it below + RemoveBucketObject(Delegate); + + if (ReplicationBuckets.Contains(UpdateHTZ)) + { + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(Delegate)); + } + else + { + FUpdateBucket & newBucket = ReplicationBuckets.Add(UpdateHTZ, FUpdateBucket(UpdateHTZ)); + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(Delegate)); + } + + if (ReplicationBuckets.Num() > 0) + bNeedsUpdate = true; + + return true; + } + + bool FUpdateBucketContainer::RemoveBucketObject(UObject * ObjectToRemove, FName FunctionName) + { + if (!ObjectToRemove || ObjectToRemove->FindFunction(FunctionName) == nullptr) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectFunction(ObjectToRemove, FunctionName)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + + // Leave the loop, this is called in add as well so we should never get duplicate entries + break; + } + } + + if (bRemovedObject) + { + break; + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::RemoveBucketObject(FDynamicBucketUpdateTickSignature &DynEvent) + { + if (!DynEvent.IsBound()) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectDelegate(DynEvent)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + + // Leave the loop, this is called in add as well so we should never get duplicate entries + break; + } + } + + if (bRemovedObject) + { + break; + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::RemoveObjectFromAllBuckets(UObject * ObjectToRemove) + { + if (!ObjectToRemove) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObject(ObjectToRemove)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + } + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::IsObjectInBucket(UObject * ObjectToRemove) + { + if (!ObjectToRemove) + return false; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObject(ObjectToRemove)) + { + return true; + } + } + } + + return false; + } + + bool FUpdateBucketContainer::IsObjectFunctionInBucket(UObject * ObjectToRemove, FName FunctionName) + { + if (!ObjectToRemove) + return false; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectFunction(ObjectToRemove, FunctionName)) + { + return true; + } + } + } + + return false; + } + + bool FUpdateBucketContainer::IsObjectDelegateInBucket(FDynamicBucketUpdateTickSignature &DynEvent) + { + if (!DynEvent.IsBound()) + return false; + + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectDelegate(DynEvent)) + { + return true; + } + } + } + + return false; + } \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp new file mode 100644 index 0000000..0d70d73 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp @@ -0,0 +1,588 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/CollisionIgnoreSubsystem.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(CollisionIgnoreSubsystem) + +#include "Components/SkeletalMeshComponent.h" +#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" +#include "VRGlobalSettings.h" + +//#include "Chaos/ParticleHandle.h" +#include "PhysicsEngine/PhysicsAsset.h" +#include "PhysicsEngine/PhysicsAsset.h" +#include "Physics/Experimental/PhysScene_Chaos.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" + +#include "Chaos/ContactModification.h" + +DEFINE_LOG_CATEGORY(VRE_CollisionIgnoreLog); + + +void FCollisionIgnoreSubsystemAsyncCallback::OnContactModification_Internal(Chaos::FCollisionContactModifier& Modifier) +{ + const FSimCallbackInputVR* Input = GetConsumerInput_Internal(); + + if (Input && Input->bIsInitialized) + { + for (Chaos::FContactPairModifierIterator ContactIterator = Modifier.Begin(); ContactIterator; ++ContactIterator) + { + if (ContactIterator.IsValid()) + { + Chaos::TVec2 Pair = ContactIterator->GetParticlePair(); + + Chaos::TPBDRigidParticleHandle* ParticleHandle0 = Pair[0]->CastToRigidParticle(); + Chaos::TPBDRigidParticleHandle* ParticleHandle1 = Pair[1]->CastToRigidParticle(); + + + // Chaos has some sort of bug here, the constraint flags are cleared, and for that matter the + // ID0 and ID1 pairs losing collision ignore shouldn't happen as they are still valid always + // Don't use contact modification until after we see if the incoming chaos fixes for constraint collision ignore + // is resolved. + if (ParticleHandle0 && ParticleHandle1) + { + // This lets us pull the transform at time of collision, collision events use the first contact + // for the information to throw out so we should be able to pull it here and keep it for that pair for the frame + //FTransform particleHandle = Chaos::FRigidTransform3(ParticleHandle0->X(), ParticleHandle0->R()); + //FTransform particleHandle2 = Chaos::FRigidTransform3(ParticleHandle0->X(), ParticleHandle1->R()); + + + //bool bHasCollisionFlag = ParticleHandle0->HasCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + //bool bHadCollisionFlag2 = ParticleHandle1->HasCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + + //if (bHasCollisionFlag && bHadCollisionFlag2) + { + FChaosParticlePair SearchPair(ParticleHandle0, ParticleHandle1); + + if (Input->ParticlePairs.Contains(SearchPair)) + { + ContactIterator->Disable(); + } + } + } + } + } + } +} + +void UCollisionIgnoreSubsystem::ConstructInput() +{ + if (ContactModifierCallback) + { + FSimCallbackInputVR* Input = ContactModifierCallback->GetProducerInputData_External(); + if (Input->bIsInitialized == false) + { + Input->bIsInitialized = true; + } + + // Clear out the pair array + Input->Reset(); + + for (TPair& CollisionPairArray : CollisionTrackedPairs) + { + for (FCollisionIgnorePair& IgnorePair : CollisionPairArray.Value.PairArray) + { + if (IgnorePair.Actor1 && IgnorePair.Actor2) + { + Input->ParticlePairs.Add(FChaosParticlePair(IgnorePair.Actor1->GetHandle_LowLevel()->CastToRigidParticle(), IgnorePair.Actor2->GetHandle_LowLevel()->CastToRigidParticle())); + } + } + } + } +} + + +void UCollisionIgnoreSubsystem::UpdateTimer(bool bChangesWereMade) +{ + RemovedPairs.Reset(); + const UVRGlobalSettings& VRSettings = *GetDefault(); + + if (CollisionTrackedPairs.Num() > 0) + { + if (!UpdateHandle.IsValid()) + { + // Setup the heartbeat on 1htz checks + GetWorld()->GetTimerManager().SetTimer(UpdateHandle, this, &UCollisionIgnoreSubsystem::CheckActiveFilters, VRSettings.CollisionIgnoreSubsystemUpdateRate, true, VRSettings.CollisionIgnoreSubsystemUpdateRate); + + if (VRSettings.bUseCollisionModificationForCollisionIgnore && !ContactModifierCallback) + { + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + // Register a callback + ContactModifierCallback = PhysScene->GetSolver()->CreateAndRegisterSimCallbackObject_External(/*true*/); + } + } + } + } + + // Need to only add input when changes are made + if (VRSettings.bUseCollisionModificationForCollisionIgnore && ContactModifierCallback && bChangesWereMade) + { + ConstructInput(); + } + } + else if (UpdateHandle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(UpdateHandle); + + if (VRSettings.bUseCollisionModificationForCollisionIgnore && ContactModifierCallback) + { + //FSimCallbackInputVR* Input = ContactModifierCallback->GetProducerInputData_External(); + //Input->bIsInitialized = false; + + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + // UnRegister a callback + PhysScene->GetSolver()->UnregisterAndFreeSimCallbackObject_External(ContactModifierCallback); + ContactModifierCallback = nullptr; + } + } + } + } +} + +void UCollisionIgnoreSubsystem::CheckActiveFilters() +{ + bool bMadeChanges = false; + + for (TPair& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (!IsValid(KeyPair.Key.Prim1) || !IsValid(KeyPair.Key.Prim2)) + { + // If we don't have a map element for this pair, then add it now + if (!RemovedPairs.Contains(KeyPair.Key)) + { + RemovedPairs.Add(KeyPair.Key, KeyPair.Value); + bMadeChanges = true; + } + + continue; // skip remaining checks as we have invalid primitives anyway + } + + // Skip this section but leave the code intact in case i need it eventually +#if false + // Now check for lost physics handles + // Implement later + if (FPhysScene* PhysScene = KeyPair.Key.Prim1->GetWorld()->GetPhysicsScene()) + { + Chaos::FIgnoreCollisionManager& IgnoreCollisionManager = PhysScene->GetSolver()->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager(); + FPhysicsCommand::ExecuteWrite(PhysScene, [&]() + { + using namespace Chaos; + for (int i = KeyPair.Value.PairArray.Num() - 1; i >= 0; i--) + { + FPhysicsActorHandle hand1 = KeyPair.Value.PairArray[i].Actor1; + FPhysicsActorHandle hand2 = KeyPair.Value.PairArray[i].Actor2; + + /*if (bIgnoreCollision) + { + if (!IgnoreCollisionManager.IgnoresCollision(ID0, ID1)) + { + TPBDRigidParticleHandle* ParticleHandle0 = ApplicableBodies[i].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + TPBDRigidParticleHandle* ParticleHandle1 = ApplicableBodies2[j].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + + if (ParticleHandle0 && ParticleHandle1) + { + ParticleHandle0->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID0, ID1); + + ParticleHandle1->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID1, ID0); + + } + } + }*/ + + if ( + (!KeyPair.Value.PairArray[i].Actor1 || !FPhysicsInterface::IsValid(KeyPair.Value.PairArray[i].Actor1)) || + (!KeyPair.Value.PairArray[i].Actor2 || !FPhysicsInterface::IsValid(KeyPair.Value.PairArray[i].Actor2)) + ) + { + + // We will either be re-initing or deleting from here so it will always involve changes + bMadeChanges = true; + + FName Bone1 = KeyPair.Value.PairArray[i].BoneName1; + FName Bone2 = KeyPair.Value.PairArray[i].BoneName2; + + FBodyInstance* Inst1 = KeyPair.Key.Prim1->GetBodyInstance(Bone1); + FBodyInstance* Inst2 = KeyPair.Key.Prim2->GetBodyInstance(Bone2); + + if (Inst1 && Inst2) + { + // We still have the bones available, lets go ahead and re-init for them + KeyPair.Value.PairArray.RemoveAt(i); + SetComponentCollisionIgnoreState(false, false, KeyPair.Key.Prim1, Bone1, KeyPair.Key.Prim2, Bone2, true, false); + } + else + { + // Its not still available, lets remove the pair. + KeyPair.Value.PairArray.RemoveAt(i); + } + } + else + { + Chaos::FUniqueIdx ID0 = KeyPair.Value.PairArray[i].Actor1->GetParticle_LowLevel()->UniqueIdx(); + Chaos::FUniqueIdx ID1 = KeyPair.Value.PairArray[i].Actor2->GetParticle_LowLevel()->UniqueIdx(); + + if (!IgnoreCollisionManager.IgnoresCollision(ID0, ID1)) + { + auto* pHandle1 = KeyPair.Value.PairArray[i].Actor1->GetHandle_LowLevel(); + auto* pHandle2 = KeyPair.Value.PairArray[i].Actor2->GetHandle_LowLevel(); + + if (pHandle1 && pHandle2) + { + TPBDRigidParticleHandle* ParticleHandle0 = pHandle1->CastToRigidParticle(); + TPBDRigidParticleHandle* ParticleHandle1 = pHandle2->CastToRigidParticle(); + + if (ParticleHandle0 && ParticleHandle1) + { + ParticleHandle0->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID0, ID1); + + ParticleHandle1->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID1, ID0); + } + } + } + } + } + }); + } +#endif + + // If there are no pairs left + if (KeyPair.Value.PairArray.Num() < 1) + { + // Try and remove it, chaos should be cleaning up the ignore setups + if (!RemovedPairs.Contains(KeyPair.Key)) + { + RemovedPairs.Add(KeyPair.Key, KeyPair.Value); + } + } + } + +/*#if WITH_CHAOS + if (FPhysScene* PhysScene2 = GetWorld()->GetPhysicsScene()) + { + Chaos::FIgnoreCollisionManager& IgnoreCollisionManager = PhysScene2->GetSolver()->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager(); + int32 ExternalTimestamp = PhysScene2->GetSolver()->GetMarshallingManager().GetExternalTimestamp_External(); + + Chaos::FIgnoreCollisionManager::FPendingMap IgnoreSet = IgnoreCollisionManager.GetPendingActivationsForGameThread(ExternalTimestamp); + Chaos::FIgnoreCollisionManager::FDeactivationSet DeactiveSet = IgnoreCollisionManager.GetPendingDeactivationsForGameThread(ExternalTimestamp); + //Chaos::FIgnoreCollisionManager::FDeactivationSet IgnoreSet = + + // Prints out the list of items currently being re-activated after one of their pairs died. + // Chaos automatically cleans up here, I don't need to do anything. + GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Pending activate: %i - Pending deativate: %i"), IgnoreSet.Num(), DeactiveSet.Num())); + } +#endif*/ + + for (const TPair& KeyPair : RemovedPairs) + { + if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + CollisionTrackedPairs[KeyPair.Key].PairArray.Empty(); + CollisionTrackedPairs.Remove(KeyPair.Key); + } + } + + UpdateTimer(bMadeChanges); +} + +void UCollisionIgnoreSubsystem::RemoveComponentCollisionIgnoreState(UPrimitiveComponent* Prim1) +{ + + if (!Prim1) + return; + + + TMap PairsToRemove; + + for (const TPair& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (KeyPair.Key.Prim1 == Prim1 || KeyPair.Key.Prim2 == Prim1) + { + // If we don't have a map element for this pair, then add it now + if (!PairsToRemove.Contains(KeyPair.Key)) + { + PairsToRemove.Add(KeyPair.Key, KeyPair.Value); + } + } + } + + + for (const TPair& KeyPair : PairsToRemove) + { + if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + for (const FCollisionIgnorePair& newIgnorePair : KeyPair.Value.PairArray) + { + // Clear out current ignores + SetComponentCollisionIgnoreState(false, false, KeyPair.Key.Prim1, newIgnorePair.BoneName1, KeyPair.Key.Prim2, newIgnorePair.BoneName2, false, false); + } + + /*if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + // Ensure we are empty + CollisionTrackedPairs[KeyPair.Key].PairArray.Empty(); + CollisionTrackedPairs.Remove(KeyPair.Key); + }*/ + } + } + + UpdateTimer(true); +} + +bool UCollisionIgnoreSubsystem::IsComponentIgnoringCollision(UPrimitiveComponent* Prim1) +{ + + if (!Prim1) + return false; + + for (const TPair& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (KeyPair.Key.Prim1 == Prim1 || KeyPair.Key.Prim2 == Prim1) + { + return true; + } + } + + return false; +} + +bool UCollisionIgnoreSubsystem::AreComponentsIgnoringCollisions(UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2) +{ + + if (!Prim1 || !Prim2) + return false; + + TSet CurrentKeys; + int numKeys = CollisionTrackedPairs.GetKeys(CurrentKeys); + + FCollisionPrimPair SearchPair; + SearchPair.Prim1 = Prim1; + SearchPair.Prim2 = Prim2; + + // This checks if we exist already as well as provides an index + if (FCollisionPrimPair* ExistingPair = CurrentKeys.Find(SearchPair)) + { + // These components are ignoring collision + return true; + } + + return false; +} + +void UCollisionIgnoreSubsystem::InitiateIgnore() +{ + +} + +bool UCollisionIgnoreSubsystem::HasCollisionIgnorePairs() +{ + return CollisionTrackedPairs.Num() > 0; +} + +void UCollisionIgnoreSubsystem::SetComponentCollisionIgnoreState(bool bIterateChildren1, bool bIterateChildren2, UPrimitiveComponent* Prim1, FName OptionalBoneName1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bIgnoreCollision, bool bCheckFilters) +{ + if (!Prim1 || !Prim2) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with invalid object(s)!!")); + return; + } + + if (Prim1->GetCollisionEnabled() == ECollisionEnabled::NoCollision || Prim2->GetCollisionEnabled() == ECollisionEnabled::NoCollision) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with one or more objects with no collision!! %s, %s"), *Prim1->GetName(), *Prim2->GetName()); + return; + } + + if (Prim1->Mobility == EComponentMobility::Static || Prim2->Mobility == EComponentMobility::Static) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with at least one static mobility object (cannot ignore collision with it)!!")); + if (bIgnoreCollision) + { + return; + } + } + + USkeletalMeshComponent* SkeleMesh = nullptr; + USkeletalMeshComponent* SkeleMesh2 = nullptr; + + if (bIterateChildren1) + { + SkeleMesh = Cast(Prim1); + } + + if (bIterateChildren2) + { + SkeleMesh2 = Cast(Prim2); + } + + struct BodyPairStore + { + FBodyInstance* BInstance; + FName BName; + + BodyPairStore(FBodyInstance* BI, FName BoneName) + { + BInstance = BI; + BName = BoneName; + } + }; + + TArray ApplicableBodies; + + if (SkeleMesh) + { + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if (PhysAsset) + { + int32 NumBodiesFound = SkeleMesh->ForEachBodyBelow(OptionalBoneName1, true, false, [PhysAsset, &ApplicableBodies](FBodyInstance* BI) + { + const FName IterBodyName = PhysAsset->SkeletalBodySetups[BI->InstanceBodyIndex]->BoneName; + ApplicableBodies.Add(BodyPairStore(BI, IterBodyName)); + }); + } + } + else + { + FBodyInstance* Inst1 = Prim1->GetBodyInstance(OptionalBoneName1); + if (Inst1) + { + ApplicableBodies.Add(BodyPairStore(Inst1, OptionalBoneName1)); + } + } + + TArray ApplicableBodies2; + if (SkeleMesh2) + { + UPhysicsAsset* PhysAsset = SkeleMesh2 ? SkeleMesh2->GetPhysicsAsset() : nullptr; + if (PhysAsset) + { + int32 NumBodiesFound = SkeleMesh2->ForEachBodyBelow(OptionalBoneName2, true, false, [PhysAsset, &ApplicableBodies2](FBodyInstance* BI) + { + const FName IterBodyName = PhysAsset->SkeletalBodySetups[BI->InstanceBodyIndex]->BoneName; + ApplicableBodies2.Add(BodyPairStore(BI, IterBodyName)); + }); + } + } + else + { + FBodyInstance* Inst1 = Prim2->GetBodyInstance(OptionalBoneName2); + if (Inst1) + { + ApplicableBodies2.Add(BodyPairStore(Inst1, OptionalBoneName2)); + } + } + + + FCollisionPrimPair newPrimPair; + newPrimPair.Prim1 = Prim1; + newPrimPair.Prim2 = Prim2; + + // Check our active filters and handle inconsistencies before we run the next logic + // (This prevents cases where null ptrs get added too) + if (bCheckFilters) + { + CheckActiveFilters(); + } + + // If we don't have a map element for this pair, then add it now + if (bIgnoreCollision && !CollisionTrackedPairs.Contains(newPrimPair)) + { + CollisionTrackedPairs.Add(newPrimPair, FCollisionIgnorePairArray()); + } + else if (!bIgnoreCollision && !CollisionTrackedPairs.Contains(newPrimPair)) + { + // Early out, we don't even have this pair to remove it + return; + } + + for (int i = 0; i < ApplicableBodies.Num(); ++i) + { + for (int j = 0; j < ApplicableBodies2.Num(); ++j) + { + if (ApplicableBodies[i].BInstance && ApplicableBodies2[j].BInstance && ApplicableBodies[i].BInstance->ActorHandle && ApplicableBodies2[j].BInstance->ActorHandle) + { + if (FPhysScene* PhysScene = Prim1->GetWorld()->GetPhysicsScene()) + { + FCollisionIgnorePair newIgnorePair; + newIgnorePair.Actor1 = ApplicableBodies[i].BInstance->ActorHandle; + newIgnorePair.BoneName1 = ApplicableBodies[i].BName; + newIgnorePair.Actor2 = ApplicableBodies2[j].BInstance->ActorHandle; + newIgnorePair.BoneName2 = ApplicableBodies2[j].BName; + + //Chaos::FUniqueIdx ID0 = ApplicableBodies[i].BInstance->ActorHandle->GetParticle_LowLevel()->UniqueIdx(); + //Chaos::FUniqueIdx ID1 = ApplicableBodies2[j].BInstance->ActorHandle->GetParticle_LowLevel()->UniqueIdx(); + + auto* pHandle1 = ApplicableBodies[i].BInstance->ActorHandle->GetHandle_LowLevel(); + auto* pHandle2 = ApplicableBodies2[j].BInstance->ActorHandle->GetHandle_LowLevel(); + + Chaos::FIgnoreCollisionManager& IgnoreCollisionManager = PhysScene->GetSolver()->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager(); + + FPhysicsCommand::ExecuteWrite(PhysScene, [&]() + { + using namespace Chaos; + + if (bIgnoreCollision && pHandle1 && pHandle2) + { + if (!IgnoreCollisionManager.IgnoresCollision(pHandle1, pHandle2)) + { + IgnoreCollisionManager.AddIgnoreCollisions(pHandle1, pHandle2); + + TSet CurrentKeys; + int numKeys = CollisionTrackedPairs.GetKeys(CurrentKeys); + + // This checks if we exist already as well as provides an index + if (FCollisionPrimPair* CurrentPair = CurrentKeys.Find(newPrimPair)) + { + // Check if the current one has the same primitive ordering as the new check + if (CurrentPair->Prim1 != newPrimPair.Prim1) + { + // If not then lets flip the elements around in order to match it + newIgnorePair.FlipElements(); + } + + CollisionTrackedPairs[newPrimPair].PairArray.AddUnique(newIgnorePair); + } + } + } + else if (pHandle1 && pHandle2) + { + if (IgnoreCollisionManager.IgnoresCollision(pHandle1, pHandle2)) + { + IgnoreCollisionManager.RemoveIgnoreCollisions(pHandle1, pHandle2); + + CollisionTrackedPairs[newPrimPair].PairArray.Remove(newIgnorePair); + if (CollisionTrackedPairs[newPrimPair].PairArray.Num() < 1) + { + CollisionTrackedPairs.Remove(newPrimPair); + } + + // If we don't have a map element for this pair, then add it now + if (!RemovedPairs.Contains(newPrimPair)) + { + RemovedPairs.Add(newPrimPair, FCollisionIgnorePairArray()); + } + RemovedPairs[newPrimPair].PairArray.AddUnique(newIgnorePair); + } + } + }); + } + } + } + } + + // Update our timer state + UpdateTimer(true); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp new file mode 100644 index 0000000..2fb79cf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp @@ -0,0 +1,677 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/OptionalRepSkeletalMeshActor.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(OptionalRepSkeletalMeshActor) + +#include "TimerManager.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "PhysicsEngine/PhysicsAsset.h" +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +UNoRepSphereComponent::UNoRepSphereComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetIsReplicatedByDefault(true); + this->PrimaryComponentTick.bCanEverTick = false; + SphereRadius = 4.0f; + SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + SetCollisionResponseToAllChannels(ECR_Ignore); + //SetAllMassScale(0.0f); Engine hates calling this in constructor + + + BodyInstance.bOverrideMass = true; + BodyInstance.SetMassOverride(0.f); +} + +void UNoRepSphereComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UNoRepSphereComponent, bReplicateMovement); + + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachParent, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachSocketName, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachChildren, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, COND_InitialOnly); + //DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); +} + +void UNoRepSphereComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + +} + +void FSkeletalMeshComponentEndPhysicsTickFunctionVR::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) +{ + //QUICK_SCOPE_CYCLE_COUNTER(FSkeletalMeshComponentEndPhysicsTickFunction_ExecuteTick); + //CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Physics); + + FActorComponentTickFunction::ExecuteTickHelper(TargetVR, /*bTickInEditor=*/ false, DeltaTime, TickType, [this](float DilatedTime) + { + TargetVR->EndPhysicsTickComponentVR(*this); + }); +} + +FString FSkeletalMeshComponentEndPhysicsTickFunctionVR::DiagnosticMessage() +{ + if (TargetVR) + { + return TargetVR->GetFullName() + TEXT("[EndPhysicsTickVR]"); + } + return TEXT("[EndPhysicsTick]"); +} + +FName FSkeletalMeshComponentEndPhysicsTickFunctionVR::DiagnosticContext(bool bDetailed) +{ + return FName(TEXT("SkeletalMeshComponentEndPhysicsTickVR")); +} + + +UInversePhysicsSkeletalMeshComponent::UInversePhysicsSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; + this->EndPhysicsTickFunction.bCanEverTick = false; + bReplicatePhysicsToAutonomousProxy = false; + + EndPhysicsTickFunctionVR.TickGroup = TG_EndPhysics; + EndPhysicsTickFunctionVR.bCanEverTick = true; + EndPhysicsTickFunctionVR.bStartWithTickEnabled = true; +} + +void UInversePhysicsSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UInversePhysicsSkeletalMeshComponent::EndPhysicsTickComponentVR(FSkeletalMeshComponentEndPhysicsTickFunctionVR& ThisTickFunction) +{ + //IMPORTANT! + // + // The decision on whether to use EndPhysicsTickComponent or not is made by ShouldRunEndPhysicsTick() + // Any changes that are made to EndPhysicsTickComponent that affect whether it should be run or not + // have to be reflected in ShouldRunEndPhysicsTick() as well + + // if physics is disabled on dedicated server, no reason to be here. + if (!bEnablePhysicsOnDedicatedServer && IsRunningDedicatedServer()) + { + FinalizeBoneTransform(); + return; + } + + if (IsRegistered() && IsSimulatingPhysics() && RigidBodyIsAwake()) + { + if (bNotifySyncComponentToRBPhysics) + { + OnSyncComponentToRBPhysics(); + } + + SyncComponentToRBPhysics(); + } + + // this used to not run if not rendered, but that causes issues such as bounds not updated + // causing it to not rendered, at the end, I think we should blend body positions + // for example if you're only simulating, this has to happen all the time + // whether looking at it or not, otherwise + // @todo better solution is to check if it has moved by changing SyncComponentToRBPhysics to return true if anything modified + // and run this if that is true or rendered + // that will at least reduce the chance of mismatch + // generally if you move your actor position, this has to happen to approximately match their bounds + if (ShouldBlendPhysicsBones()) + { + if (IsRegistered()) + { + BlendInPhysicsInternalVR(ThisTickFunction); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::BlendInPhysicsInternalVR(FTickFunction& ThisTickFunction) +{ + check(IsInGameThread()); + + // Can't do anything without a SkeletalMesh + if (!GetSkinnedAsset()) + { + return; + } + + // We now have all the animations blended together and final relative transforms for each bone. + // If we don't have or want any physics, we do nothing. + if (Bodies.Num() > 0 && CollisionEnabledHasPhysics(GetCollisionEnabled())) + { + //HandleExistingParallelEvaluationTask(/*bBlockOnTask = */ true, /*bPerformPostAnimEvaluation =*/ true); + // start parallel work + //check(!IsValidRef(ParallelAnimationEvaluationTask)); + + const bool bParallelBlend = false;// !!CVarUseParallelBlendPhysics.GetValueOnGameThread() && FApp::ShouldUseThreadingForPerformance(); + if (bParallelBlend) + { + /*SwapEvaluationContextBuffers(); + + ParallelAnimationEvaluationTask = TGraphTask::CreateTask().ConstructAndDispatchWhenReady(this); + + // set up a task to run on the game thread to accept the results + FGraphEventArray Prerequistes; + Prerequistes.Add(ParallelAnimationEvaluationTask); + + check(!IsValidRef(ParallelBlendPhysicsCompletionTask)); + ParallelBlendPhysicsCompletionTask = TGraphTask::CreateTask(&Prerequistes).ConstructAndDispatchWhenReady(this); + + ThisTickFunction.GetCompletionHandle()->DontCompleteUntil(ParallelBlendPhysicsCompletionTask);*/ + } + else + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + PerformBlendPhysicsBonesVR(RequiredBones, GetEditableComponentSpaceTransforms(), BoneSpaceTransforms); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + FinalizeAnimationUpdateVR(); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::FinalizeAnimationUpdateVR() +{ + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate); + + // Flip bone buffer and send 'post anim' notification + FinalizeBoneTransform(); + + if (!bSimulationUpdatesChildTransforms || !IsSimulatingPhysics()) //If we simulate physics the call to MoveComponent already updates the children transforms. If we are confident that animation will not be needed this can be skipped. TODO: this should be handled at the scene component layer + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateChildTransforms); + + // Update Child Transform - The above function changes bone transform, so will need to update child transform + // But only children attached to us via a socket. + UpdateChildTransforms(EUpdateTransformFlags::OnlyUpdateIfUsingSocket); + } + + if (bUpdateOverlapsOnAnimationFinalize) + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateOverlaps); + + // animation often change overlap. + UpdateOverlaps(); + } + + // update bounds + // *NOTE* This is a private var, I have to remove it for this temp fix + /*if (bSkipBoundsUpdateWhenInterpolating) + { + if (AnimEvaluationContext.bDoEvaluation) + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateBounds); + // Cached local bounds are now out of date + InvalidateCachedBounds(); + + UpdateBounds(); + } + } + else*/ + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateBounds); + // Cached local bounds are now out of date + InvalidateCachedBounds(); + + UpdateBounds(); + } + + // Need to send new bounds to + MarkRenderTransformDirty(); + + // New bone positions need to be sent to render thread + MarkRenderDynamicDataDirty(); + + // If we have any Slave Components, they need to be refreshed as well. + RefreshFollowerComponents(); +} + +void UInversePhysicsSkeletalMeshComponent::GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) +{ + UPhysicsAsset* PhysicsAsset = GetPhysicsAsset(); + + for (int32 BodyIdx = 0; BodyIdx < Bodies.Num(); ++BodyIdx) + { + FBodyInstance* BI = Bodies[BodyIdx]; + if (BI && (BI->WeldParent != nullptr || (bIncludingAutoWeld && BI->bAutoWeld))) + { + OutWeldedBodies.Add(BI); + if (PhysicsAsset) + { + if (UBodySetup* PhysicsAssetBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIdx]) + { + OutLabels.Add(PhysicsAssetBodySetup->BoneName); + } + else + { + OutLabels.Add(NAME_None); + } + } + else + { + OutLabels.Add(NAME_None); + } + + } + } + + for (USceneComponent* Child : GetAttachChildren()) + { + if (UPrimitiveComponent* PrimChild = Cast(Child)) + { + PrimChild->GetWeldedBodies(OutWeldedBodies, OutLabels, bIncludingAutoWeld); + } + } +} + +FBodyInstance* UInversePhysicsSkeletalMeshComponent::GetBodyInstance(FName BoneName, bool bGetWelded, int32) const +{ + UPhysicsAsset* const PhysicsAsset = GetPhysicsAsset(); + FBodyInstance* BodyInst = NULL; + + if (PhysicsAsset != NULL) + { + // A name of NAME_None indicates 'root body' + if (BoneName == NAME_None) + { + if (Bodies.IsValidIndex(RootBodyData.BodyIndex)) + { + BodyInst = Bodies[RootBodyData.BodyIndex]; + } + } + // otherwise, look for the body + else + { + int32 BodyIndex = PhysicsAsset->FindBodyIndex(BoneName); + if (Bodies.IsValidIndex(BodyIndex)) + { + BodyInst = Bodies[BodyIndex]; + } + } + + BodyInst = (bGetWelded && BodyInstance.WeldParent) ? BodyInstance.WeldParent : BodyInst; + } + + return BodyInst; +} + +struct FAssetWorldBoneTM +{ + FTransform TM; // Should never contain scaling. + bool bUpToDate; // If this equals PhysAssetUpdateNum, then the matrix is up to date. +}; + +typedef TArray> TAssetWorldBoneTMArray; + +void UpdateWorldBoneTMVR(TAssetWorldBoneTMArray& WorldBoneTMs, const TArray& InBoneSpaceTransforms, int32 BoneIndex, USkeletalMeshComponent* SkelComp, const FTransform& LocalToWorldTM, const FVector& Scale3D) +{ + // If its already up to date - do nothing + if (WorldBoneTMs[BoneIndex].bUpToDate) + { + return; + } + + FTransform ParentTM, RelTM; + if (BoneIndex == 0) + { + // If this is the root bone, we use the mesh component LocalToWorld as the parent transform. + ParentTM = LocalToWorldTM; + } + else + { + // If not root, use our cached world-space bone transforms. + int32 ParentIndex = SkelComp->GetSkeletalMeshAsset()->GetRefSkeleton().GetParentIndex(BoneIndex); + UpdateWorldBoneTMVR(WorldBoneTMs, InBoneSpaceTransforms, ParentIndex, SkelComp, LocalToWorldTM, Scale3D); + ParentTM = WorldBoneTMs[ParentIndex].TM; + } + + if (InBoneSpaceTransforms.IsValidIndex(BoneIndex)) + { + RelTM = InBoneSpaceTransforms[BoneIndex]; + RelTM.ScaleTranslation(Scale3D); + + WorldBoneTMs[BoneIndex].TM = RelTM * ParentTM; + WorldBoneTMs[BoneIndex].bUpToDate = true; + } +} + +void UInversePhysicsSkeletalMeshComponent::PerformBlendPhysicsBonesVR(const TArray& InRequiredBones, TArray& InOutComponentSpaceTransforms, TArray& InOutBoneSpaceTransforms) +{ + //SCOPE_CYCLE_COUNTER(STAT_BlendInPhysics); + // Get drawscale from Owner (if there is one) + FVector TotalScale3D = GetComponentTransform().GetScale3D(); + FVector RecipScale3D = TotalScale3D.Reciprocal(); + + UPhysicsAsset* const PhysicsAsset = GetPhysicsAsset(); + check(PhysicsAsset); + + if (InOutComponentSpaceTransforms.Num() == 0) + { + return; + } + + // Get the scene, and do nothing if we can't get one. + FPhysScene* PhysScene = nullptr; + if (GetWorld() != nullptr) + { + PhysScene = GetWorld()->GetPhysicsScene(); + } + + if (PhysScene == nullptr) + { + return; + } + + FMemMark Mark(FMemStack::Get()); + // Make sure scratch space is big enough. + TAssetWorldBoneTMArray WorldBoneTMs; + WorldBoneTMs.AddZeroed(InOutComponentSpaceTransforms.Num()); + + FTransform LocalToWorldTM = GetComponentTransform(); + + // PhysAnim.cpp - PerformBlendPhysicsBones + // This fixes the simulated inversed scaled skeletal mesh bug + LocalToWorldTM.SetScale3D(LocalToWorldTM.GetScale3D().GetSignVector()); + LocalToWorldTM.NormalizeRotation(); + + // Original implementation stomps negative scale values + //LocalToWorldTM.RemoveScaling(); + + struct FBodyTMPair + { + FBodyInstance* BI; + FTransform TM; + }; + + FPhysicsCommand::ExecuteRead(this, [&]() + { + bool bSetParentScale = false; + const bool bSimulatedRootBody = Bodies.IsValidIndex(RootBodyData.BodyIndex) && Bodies[RootBodyData.BodyIndex]->IsInstanceSimulatingPhysics(); + const FTransform NewComponentToWorld = bSimulatedRootBody ? GetComponentTransformFromBodyInstance(Bodies[RootBodyData.BodyIndex]) : FTransform::Identity; + + // For each bone - see if we need to provide some data for it. + for (int32 i = 0; i < InRequiredBones.Num(); i++) + { + int32 BoneIndex = InRequiredBones[i]; + + // See if this is a physics bone.. + int32 BodyIndex = PhysicsAsset->FindBodyIndex(GetSkeletalMeshAsset()->GetRefSkeleton().GetBoneName(BoneIndex)); + // need to update back to physics so that physics knows where it was after blending + FBodyInstance* PhysicsAssetBodyInstance = nullptr; + + // If so - get its world space matrix and its parents world space matrix and calc relative atom. + if (BodyIndex != INDEX_NONE) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + // tracking down TTP 280421. Remove this if this doesn't happen. + if (!ensure(Bodies.IsValidIndex(BodyIndex))) + { + UE_LOG(LogPhysics, Warning, TEXT("%s(Mesh %s, PhysicsAsset %s)"), + *GetName(), *GetNameSafe(GetSkeletalMeshAsset()), *GetNameSafe(PhysicsAsset)); + UE_LOG(LogPhysics, Warning, TEXT(" - # of BodySetup (%d), # of Bodies (%d), Invalid BodyIndex(%d)"), + PhysicsAsset->SkeletalBodySetups.Num(), Bodies.Num(), BodyIndex); + continue; + } +#endif + PhysicsAssetBodyInstance = Bodies[BodyIndex]; + + //if simulated body copy back and blend with animation + if (PhysicsAssetBodyInstance->IsInstanceSimulatingPhysics()) + { + FTransform PhysTM = PhysicsAssetBodyInstance->GetUnrealWorldTransform_AssumesLocked(); + + // Store this world-space transform in cache. + WorldBoneTMs[BoneIndex].TM = PhysTM; + WorldBoneTMs[BoneIndex].bUpToDate = true; + + float UsePhysWeight = (bBlendPhysics) ? 1.f : PhysicsAssetBodyInstance->PhysicsBlendWeight; + + // if the body instance is disabled, then we want to use the animation transform and ignore the physics one + if (PhysicsAssetBodyInstance->IsPhysicsDisabled()) + { + UsePhysWeight = 0.0f; + } + + // Find this bones parent matrix. + FTransform ParentWorldTM; + + // if we wan't 'full weight' we just find + if (UsePhysWeight > 0.f) + { + if (!(ensure(InOutBoneSpaceTransforms.Num()))) + { + continue; + } + + if (BoneIndex == 0) + { + ParentWorldTM = LocalToWorldTM; + } + else + { + // If not root, get parent TM from cache (making sure its up-to-date). + int32 ParentIndex = GetSkeletalMeshAsset()->GetRefSkeleton().GetParentIndex(BoneIndex); + UpdateWorldBoneTMVR(WorldBoneTMs, InOutBoneSpaceTransforms, ParentIndex, this, LocalToWorldTM, TotalScale3D); + ParentWorldTM = WorldBoneTMs[ParentIndex].TM; + } + + + // Then calc rel TM and convert to atom. + FTransform RelTM = PhysTM.GetRelativeTransform(ParentWorldTM); + RelTM.RemoveScaling(); + FQuat RelRot(RelTM.GetRotation()); + FVector RelPos = RecipScale3D * RelTM.GetLocation(); + FTransform PhysAtom = FTransform(RelRot, RelPos, InOutBoneSpaceTransforms[BoneIndex].GetScale3D()); + + // Now blend in this atom. See if we are forcing this bone to always be blended in + InOutBoneSpaceTransforms[BoneIndex].Blend(InOutBoneSpaceTransforms[BoneIndex], PhysAtom, UsePhysWeight); + + if (!bSetParentScale) + { + //We must update RecipScale3D based on the atom scale of the root + TotalScale3D *= InOutBoneSpaceTransforms[0].GetScale3D(); + RecipScale3D = TotalScale3D.Reciprocal(); + bSetParentScale = true; + } + + } + } + } + + if (!(ensure(BoneIndex < InOutComponentSpaceTransforms.Num()))) + { + continue; + } + + // Update SpaceBases entry for this bone now + if (BoneIndex == 0) + { + if (!(ensure(InOutBoneSpaceTransforms.Num()))) + { + continue; + } + InOutComponentSpaceTransforms[0] = InOutBoneSpaceTransforms[0]; + } + else + { + if (bLocalSpaceKinematics || BodyIndex == INDEX_NONE || Bodies[BodyIndex]->IsInstanceSimulatingPhysics()) + { + if (!(ensure(BoneIndex < InOutBoneSpaceTransforms.Num()))) + { + continue; + } + const int32 ParentIndex = GetSkeletalMeshAsset()->GetRefSkeleton().GetParentIndex(BoneIndex); + InOutComponentSpaceTransforms[BoneIndex] = InOutBoneSpaceTransforms[BoneIndex] * InOutComponentSpaceTransforms[ParentIndex]; + + /** + * Normalize rotations. + * We want to remove any loss of precision due to accumulation of error. + * i.e. A componentSpace transform is the accumulation of all of its local space parents. The further down the chain, the greater the error. + * SpaceBases are used by external systems, we feed this to Physics, send this to gameplay through bone and socket queries, etc. + * So this is a good place to make sure all transforms are normalized. + */ + InOutComponentSpaceTransforms[BoneIndex].NormalizeRotation(); + } + else if (bSimulatedRootBody) + { + InOutComponentSpaceTransforms[BoneIndex] = Bodies[BodyIndex]->GetUnrealWorldTransform_AssumesLocked().GetRelativeTransform(NewComponentToWorld); + } + } + } + }); //end scope for read lock + +} + +void UInversePhysicsSkeletalMeshComponent::RegisterEndPhysicsTick(bool bRegister) +{ + // For testing if the engine fix is live yet or not + //return Super::RegisterEndPhysicsTick(bRegister); + + if (bRegister != EndPhysicsTickFunctionVR.IsTickFunctionRegistered()) + { + if (bRegister) + { + if (SetupActorComponentTickFunction(&EndPhysicsTickFunctionVR)) + { + EndPhysicsTickFunctionVR.TargetVR = this; + // Make sure our EndPhysicsTick gets called after physics simulation is finished + UWorld* World = GetWorld(); + if (World != nullptr) + { + EndPhysicsTickFunctionVR.AddPrerequisite(World, World->EndPhysicsTickFunction); + } + } + } + else + { + EndPhysicsTickFunctionVR.UnRegisterTickFunction(); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + //CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Animation); + + bool bShouldRunPhysTick = (bEnablePhysicsOnDedicatedServer || !IsNetMode(NM_DedicatedServer)) && // Early out if we are on a dedicated server and not running physics. + ((IsSimulatingPhysics() && RigidBodyIsAwake()) || ShouldBlendPhysicsBones()); + + RegisterEndPhysicsTick(PrimaryComponentTick.IsTickFunctionRegistered() && bShouldRunPhysTick); + //UpdateEndPhysicsTickRegisteredState(); + RegisterClothTick(PrimaryComponentTick.IsTickFunctionRegistered() && ShouldRunClothTick()); + //UpdateClothTickRegisteredState(); + + + // If we are suspended, we will not simulate clothing, but as clothing is simulated in local space + // relative to a root bone we need to extract simulation positions as this bone could be animated. + /*if (bClothingSimulationSuspended && this->GetClothingSimulation() && this->GetClothingSimulation()->ShouldSimulate()) + { + //CSV_SCOPED_TIMING_STAT(Animation, Cloth); + + this->GetClothingSimulation()->GetSimulationData(CurrentSimulationData, this, Cast(MasterPoseComponent.Get())); + }*/ + + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + PendingRadialForces.Reset(); + + // Update bOldForceRefPose + bOldForceRefPose = bForceRefpose; + + + static const auto CVarAnimationDelaysEndGroup = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("tick.AnimationDelaysEndGroup")); + /** Update the end group and tick priority */ + const bool bDoLateEnd = CVarAnimationDelaysEndGroup->GetValueOnGameThread() > 0; + const bool bRequiresPhysics = EndPhysicsTickFunctionVR.IsTickFunctionRegistered(); + const ETickingGroup EndTickGroup = bDoLateEnd && !bRequiresPhysics ? TG_PostPhysics : TG_PrePhysics; + if (ThisTickFunction) + { + ThisTickFunction->EndTickGroup = TG_PostPhysics;// EndTickGroup; + + // Note that if animation is so long that we are blocked in EndPhysics we may want to reduce the priority. However, there is a risk that this function will not go wide early enough. + // This requires profiling and is very game dependent so cvar for now makes sense + + static const auto CVarHiPriSkinnedMeshesTicks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("tick.HiPriSkinnedMeshes")); + bool bDoHiPri = CVarHiPriSkinnedMeshesTicks->GetValueOnGameThread() > 0; + if (ThisTickFunction->bHighPriority != bDoHiPri) + { + ThisTickFunction->SetPriorityIncludingPrerequisites(bDoHiPri); + } + } + + // If we are waiting for ParallelEval to complete or if we require Physics, + // then FinalizeBoneTransform will be called and Anim events will be dispatched there. + // We prefer doing it there so these events are triggered once we have a new updated pose. + // Note that it's possible that FinalizeBoneTransform has already been called here if not using ParallelUpdate. + // or it's possible that it hasn't been called at all if we're skipping Evaluate due to not being visible. + // ConditionallyDispatchQueuedAnimEvents will catch that and only Dispatch events if not already done. + if (!IsRunningParallelEvaluation() && !bRequiresPhysics) + { + ///////////////////////////////////////////////////////////////////////////// + // Notify / Event Handling! + // This can do anything to our component (including destroy it) + // Any code added after this point needs to take that into account + ///////////////////////////////////////////////////////////////////////////// + + ConditionallyDispatchQueuedAnimEvents(); + } +} + +void UInversePhysicsSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UInversePhysicsSkeletalMeshComponent, bReplicateMovement); +} + +AOptionalRepGrippableSkeletalMeshActor::AOptionalRepGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("SkeletalMeshComponent0"))) +{ + bIgnoreAttachmentReplication = false; + bIgnorePhysicsReplication = false; +} + +void AOptionalRepGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AOptionalRepGrippableSkeletalMeshActor, bIgnoreAttachmentReplication); + DOREPLIFETIME(AOptionalRepGrippableSkeletalMeshActor, bIgnorePhysicsReplication); + + if (bIgnoreAttachmentReplication) + { + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(AActor, AttachmentReplication, COND_InitialOnly); + } + //DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); +} + +void AOptionalRepGrippableSkeletalMeshActor::OnRep_ReplicateMovement() +{ + if (bIgnorePhysicsReplication) + { + return; + } + + Super::OnRep_ReplicateMovement(); +} + +void AOptionalRepGrippableSkeletalMeshActor::PostNetReceivePhysicState() +{ + if (bIgnorePhysicsReplication) + { + return; + } + + Super::PostNetReceivePhysicState(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp new file mode 100644 index 0000000..a874925 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp @@ -0,0 +1,1310 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRAIPerceptionOverrides.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRAIPerceptionOverrides) + +#include "EngineDefines.h" +#include "EngineGlobals.h" +#include "CollisionQueryParams.h" +//#include "Engine/Engine.h" +#include "AISystem.h" +#include "AIHelpers.h" +#include "Perception/AIPerceptionComponent.h" +#include "VisualLogger/VisualLogger.h" +#include "Perception/AISightTargetInterface.h" +#include "Perception/AISenseConfig_Sight.h" +#include "Perception/AIPerceptionSystem.h" + +#if WITH_GAMEPLAY_DEBUGGER +#include "GameplayDebuggerTypes.h" +#include "GameplayDebuggerCategory.h" +#endif +DEFINE_LOG_CATEGORY(LogAIPerceptionVR); + +#define AISENSE_SIGHT_TIMESLICING_DEBUG 0 + +#define DO_SIGHT_VLOGGINGVR (0 && ENABLE_VISUAL_LOG) + +#if DO_SIGHT_VLOGGINGVR +#define SIGHT_LOG_SEGMENTVR UE_VLOG_SEGMENT +#define SIGHT_LOG_LOCATIONVR UE_VLOG_LOCATION +#else +#define SIGHT_LOG_SEGMENTVR(...) +#define SIGHT_LOG_LOCATIONVR(...) +#endif // DO_SIGHT_VLOGGING + +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight"), STAT_AI_Sense_Sight, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Update Sort"), STAT_AI_Sense_Sight_UpdateSort, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Compute visibility"), STAT_AI_Sense_Sight_ComputeVisibility, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Query operations"), STAT_AI_Sense_Sight_QueryOperations, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Listener Update"), STAT_AI_Sense_Sight_ListenerUpdate, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Register Target"), STAT_AI_Sense_Sight_RegisterTarget, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove By Listener"), STAT_AI_Sense_Sight_RemoveByListener, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove To Target"), STAT_AI_Sense_Sight_RemoveToTarget, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Process pending result"), STAT_AI_Sense_Sight_ProcessPendingQuery, STATGROUP_AI); + + + +static const int32 DefaultMaxTracesPerTick = 6; +static const int32 DefaultMaxAsyncTracesPerTick = 10; +static const int32 DefaultMinQueriesPerTimeSliceCheck = 40; +static const float DefaultPendingQueriesBudgetReductionRatio = 0.5f; +static const bool bDefaultUseAsynchronousTraceForDefaultSightQueries = false; +static const float DefaultStimulusStrength = 1.f; + +enum class EForEachResult : uint8 +{ + Break, + Continue, +}; + +template +EForEachResult ForEach(T& Array, const PREDICATE_CLASS& Predicate) +{ + for (typename T::ElementType& Element : Array) + { + if (Predicate(Element) == EForEachResult::Break) + { + return EForEachResult::Break; + } + } + return EForEachResult::Continue; +} + +enum EReverseForEachResult : uint8 +{ + UnTouched, + Modified, +}; + +template +EReverseForEachResult ReverseForEach(T& Array, const PREDICATE_CLASS& Predicate) +{ + EReverseForEachResult RetVal = EReverseForEachResult::UnTouched; + for (int32 Index = Array.Num() - 1; Index >= 0; --Index) + { + if (Predicate(Array, Index) == EReverseForEachResult::Modified) + { + RetVal = EReverseForEachResult::Modified; + } + } + return RetVal; +} + +//----------------------------------------------------------------------// +// helpers +//----------------------------------------------------------------------// +FORCEINLINE_DEBUGGABLE bool CheckIsTargetInSightPie(const FPerceptionListener& Listener, const UAISense_Sight_VR::FDigestedSightProperties& DigestedProps, const FVector& TargetLocation, const float SightRadiusSq) +{ + if (FVector::DistSquared(Listener.CachedLocation, TargetLocation) <= SightRadiusSq) + { + const FVector DirectionToTarget = (TargetLocation - Listener.CachedLocation).GetUnsafeNormal(); + return FVector::DotProduct(DirectionToTarget, Listener.CachedDirection) > DigestedProps.PeripheralVisionAngleCos; + } + + return false; +} + +//----------------------------------------------------------------------// +// FAISightTargetVR +//----------------------------------------------------------------------// +const FAISightTargetVR::FTargetId FAISightTargetVR::InvalidTargetId = FAISystem::InvalidUnsignedID; + +FAISightTargetVR::FAISightTargetVR(AActor* InTarget, FGenericTeamId InTeamId) + : Target(InTarget), SightTargetInterface(NULL), TeamId(InTeamId) +{ + if (InTarget) + { + TargetId = InTarget->GetUniqueID(); + } + else + { + TargetId = InvalidTargetId; + } +} + +//----------------------------------------------------------------------// +// FDigestedSightProperties +//----------------------------------------------------------------------// +UAISense_Sight_VR::FDigestedSightProperties::FDigestedSightProperties(const UAISenseConfig_Sight_VR& SenseConfig) +{ + SightRadiusSq = FMath::Square(SenseConfig.SightRadius + SenseConfig.PointOfViewBackwardOffset); + LoseSightRadiusSq = FMath::Square(SenseConfig.LoseSightRadius + SenseConfig.PointOfViewBackwardOffset); + PointOfViewBackwardOffset = SenseConfig.PointOfViewBackwardOffset; + NearClippingRadiusSq = FMath::Square(SenseConfig.NearClippingRadius); + PeripheralVisionAngleCos = FMath::Cos(FMath::Clamp(FMath::DegreesToRadians(SenseConfig.PeripheralVisionAngleDegrees), 0.f, PI)); + AffiliationFlags = SenseConfig.DetectionByAffiliation.GetAsFlags(); + // keep the special value of FAISystem::InvalidRange (-1.f) if it's set. + AutoSuccessRangeSqFromLastSeenLocation = (SenseConfig.AutoSuccessRangeFromLastSeenLocation == FAISystem::InvalidRange) ? FAISystem::InvalidRange : FMath::Square(SenseConfig.AutoSuccessRangeFromLastSeenLocation); +} + +UAISense_Sight_VR::FDigestedSightProperties::FDigestedSightProperties() + : PeripheralVisionAngleCos(0.f), SightRadiusSq(-1.f), AutoSuccessRangeSqFromLastSeenLocation(FAISystem::InvalidRange), LoseSightRadiusSq(-1.f), PointOfViewBackwardOffset(0.0f), NearClippingRadiusSq(0.0f), AffiliationFlags(-1) +{} + + +//----------------------------------------------------------------------// +// UAISense_Sight_VR +//----------------------------------------------------------------------// +UAISense_Sight_VR::UAISense_Sight_VR(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , MaxTracesPerTick(DefaultMaxTracesPerTick) + , MaxAsyncTracesPerTick(DefaultMaxAsyncTracesPerTick) + , MinQueriesPerTimeSliceCheck(DefaultMinQueriesPerTimeSliceCheck) + , MaxTimeSlicePerTick(0.005) // 5ms + , HighImportanceQueryDistanceThreshold(300.f) + , MaxQueryImportance(60.f) + , SightLimitQueryImportance(10.f) + , PendingQueriesBudgetReductionRatio(DefaultPendingQueriesBudgetReductionRatio) + , bUseAsynchronousTraceForDefaultSightQueries(bDefaultUseAsynchronousTraceForDefaultSightQueries) +{ + if (HasAnyFlags(RF_ClassDefaultObject) == false) + { + UAISenseConfig_Sight_VR* SightConfigCDO = GetMutableDefault(); + SightConfigCDO->Implementation = UAISense_Sight_VR::StaticClass(); + + OnNewListenerDelegate.BindUObject(this, &UAISense_Sight_VR::OnNewListenerImpl); + OnListenerUpdateDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerUpdateImpl); + OnListenerRemovedDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerRemovedImpl); + + OnPendingCanBeSeenQueryProcessedDelegate.BindUObject(this, &UAISense_Sight_VR::OnPendingCanBeSeenQueryProcessed); + OnPendingTraceQueryProcessedDelegate.BindUObject(this, &UAISense_Sight_VR::OnPendingTraceQueryProcessed); + } + + NotifyType = EAISenseNotifyType::OnPerceptionChange; + + bAutoRegisterAllPawnsAsSources = true; + bNeedsForgettingNotification = true; + + DefaultSightCollisionChannel = GET_AI_CONFIG_VAR(DefaultSightCollisionChannel); +} + +FORCEINLINE_DEBUGGABLE float UAISense_Sight_VR::CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const +{ + const FVector::FReal DistanceSq = FVector::DistSquared(Listener.CachedLocation, TargetLocation); + return DistanceSq <= HighImportanceDistanceSquare ? MaxQueryImportance + : static_cast(FMath::Clamp((SightLimitQueryImportance - MaxQueryImportance) / SightRadiusSq * DistanceSq + MaxQueryImportance, 0.f, MaxQueryImportance)); +} + +void UAISense_Sight_VR::PostInitProperties() +{ + Super::PostInitProperties(); + HighImportanceDistanceSquare = FMath::Square(HighImportanceQueryDistanceThreshold); +} + +#if WITH_EDITOR +void UAISenseConfig_Sight_VR::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) +{ + static const FName NAME_AutoSuccessRangeFromLastSeenLocation = GET_MEMBER_NAME_CHECKED(UAISenseConfig_Sight_VR, AutoSuccessRangeFromLastSeenLocation); + + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property) + { + const FName PropName = PropertyChangedEvent.Property->GetFName(); + if (PropName == NAME_AutoSuccessRangeFromLastSeenLocation) + { + if (AutoSuccessRangeFromLastSeenLocation < 0) + { + AutoSuccessRangeFromLastSeenLocation = FAISystem::InvalidRange; + } + } + } +} +#endif // WITH_EDITOR + +bool UAISense_Sight_VR::ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQueryVR* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const +{ + OutStimulusStrength = 1.0f; + + if ((PropDigest.AutoSuccessRangeSqFromLastSeenLocation != FAISystem::InvalidRange) && (SightQuery->LastSeenLocation != FAISystem::InvalidLocation)) + { + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast(TargetActor); + const FVector::FReal DistanceToLastSeenLocationSq = FVector::DistSquared(VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(), SightQuery->LastSeenLocation); + return (DistanceToLastSeenLocationSq <= PropDigest.AutoSuccessRangeSqFromLastSeenLocation); + } + + return false; +} + + +namespace UE::AISense_SightVR +{ +#if AISENSE_SIGHT_TIMESLICING_DEBUG + struct FTimingSlicingInfo + { + FTimingSlicingInfo() + { + Start(); + } + + double StartTime = 0.; + double EndTime = 0.; + + int32 InRangeCount = 0; + int32 OutOfRangeCount = 0; + + float InRangeAgeSum = 0.f; + float OutOfRangeAgeSum = 0.f; + + void Start() { StartTime = FPlatformTime::Seconds(); } + void Stop() { EndTime = FPlatformTime::Seconds(); } + + void PushQueryInfo(const bool bIsInRange, const float Age) + { + if (bIsInRange) + { + ++InRangeCount; + InRangeAgeSum += Age; + } + else + { + ++OutOfRangeCount; + OutOfRangeAgeSum += Age; + } + } + + FString ToString() const + { + FString Info = FString::Format(TEXT("in {0} seconds"), { EndTime - StartTime }); + if (InRangeCount > 0) + { + Info.Append(FString::Format(TEXT("[{0} InRange Age:{1}]"), { InRangeCount, InRangeAgeSum / InRangeCount })); + } + if (OutOfRangeCount > 0) + { + Info.Append(FString::Format(TEXT("[{0} OutOfRange Age:{1}]"), { OutOfRangeCount, OutOfRangeAgeSum / OutOfRangeCount })); + } + return Info; + } + }; +} +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + +bool IsTraceConsideredVisible(const FHitResult* HitResult, const AActor* TargetActor) +{ + if (HitResult == nullptr) + { + return true; + } + + const AActor* HitResultActor = HitResult->HitObjectHandle.FetchActor(); + return (HitResultActor ? HitResultActor->IsOwnedBy(TargetActor) : false); +} +} + +float UAISense_Sight_VR::Update() +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight); + + UWorld* World = GEngine->GetWorldFromContextObject(GetPerceptionSystem()->GetOuter(), EGetWorldErrorMode::LogAndReturnNull); + + if (World == nullptr) + { + return SuspendNextUpdate; + } + + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + // sort Sight Queries + { + auto RecalcScore = [](FAISightQueryVR& SightQuery)->EForEachResult + { + SightQuery.RecalcScore(); + return EForEachResult::Continue; + }; + + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_UpdateSort); + // Sort out of range queries + if (bSightQueriesOutOfRangeDirty) + { + ForEach(SightQueriesOutOfRange, RecalcScore); + SightQueriesOutOfRange.Sort(FAISightQueryVR::FSortPredicate()); + NextOutOfRangeIndex = 0; + bSightQueriesOutOfRangeDirty = false; + } + + // Sort in range queries + ForEach(SightQueriesInRange, RecalcScore); + SightQueriesInRange.Sort(FAISightQueryVR::FSortPredicate()); + } + + int32 TracesCount = 0; + int32 AsyncTracesCount = FMath::Max(0, static_cast(PendingQueriesBudgetReductionRatio * SightQueriesPending.Num())); // pending queries should be requesting async collisions traces at this frame, so we might want to restrain ourself in this update + int32 NumQueriesProcessed = 0; + const double TimeSliceEnd = FPlatformTime::Seconds() + MaxTimeSlicePerTick; + bool bHitTimeSliceLimit = false; +#if AISENSE_SIGHT_TIMESLICING_DEBUG + UE::AISense_SightVR::FTimingSlicingInfo SlicingInfo; +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + static const int32 InitialInvalidItemsSize = 16; + enum class EOperationType : uint8 + { + Remove, + SwapList, + MoveToPending + }; + struct FQueryOperation + { + FQueryOperation(bool bInInRange, EOperationType InOpType, int32 InIndex) : bInRange(bInInRange), OpType(InOpType), Index(InIndex) {} + bool bInRange; + EOperationType OpType; + int32 Index; + }; + TArray QueryOperations; + TArray InvalidTargets; + QueryOperations.Reserve(InitialInvalidItemsSize); + InvalidTargets.Reserve(InitialInvalidItemsSize); + + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + + int32 InRangeItr = 0; + int32 OutOfRangeItr = 0; + for (int32 QueryIndex = 0; QueryIndex < SightQueriesInRange.Num() + SightQueriesOutOfRange.Num(); ++QueryIndex) + { + // Time slice limit check - spread out checks to every N queries so we don't spend more time checking timer than doing work + NumQueriesProcessed++; + if ((NumQueriesProcessed % MinQueriesPerTimeSliceCheck) == 0 && FPlatformTime::Seconds() > TimeSliceEnd) + { + bHitTimeSliceLimit = true; + } + + if (bHitTimeSliceLimit || TracesCount >= MaxTracesPerTick || AsyncTracesCount >= MaxAsyncTracesPerTick) + { + break; + } + + // Calculate next in range query + int32 InRangeIndex = SightQueriesInRange.IsValidIndex(InRangeItr) ? InRangeItr : INDEX_NONE; + FAISightQueryVR* InRangeQuery = InRangeIndex != INDEX_NONE ? &SightQueriesInRange[InRangeIndex] : nullptr; + + // Calculate next out of range query + int32 OutOfRangeIndex = SightQueriesOutOfRange.IsValidIndex(OutOfRangeItr) ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : INDEX_NONE; + FAISightQueryVR* OutOfRangeQuery = OutOfRangeIndex != INDEX_NONE ? &SightQueriesOutOfRange[OutOfRangeIndex] : nullptr; + if (OutOfRangeQuery) + { + OutOfRangeQuery->RecalcScore(); + } + + // Compare to real find next query + const bool bIsInRangeQuery = (InRangeQuery && OutOfRangeQuery) ? FAISightQueryVR::FSortPredicate()(*InRangeQuery, *OutOfRangeQuery) : !OutOfRangeQuery; + FAISightQueryVR* SightQuery = bIsInRangeQuery ? InRangeQuery : OutOfRangeQuery; + ensure(SightQuery); + +#if AISENSE_SIGHT_TIMESLICING_DEBUG + SlicingInfo.PushQueryInfo(bIsInRangeQuery, SightQuery->GetAge()); +#endif //AISENSE_SIGHT_TIMESLICING_DEBUG + + bIsInRangeQuery ? ++InRangeItr : ++OutOfRangeItr; + + FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId]; + FAISightTargetVR& Target = ObservedTargets[SightQuery->TargetId]; + + AActor* TargetActor = Target.Target.Get(); + UAIPerceptionComponent* ListenerPtr = Listener.Listener.Get(); + ensure(ListenerPtr); + + + // @todo figure out what should we do if not valid + if (TargetActor && ListenerPtr) + { + const FDigestedSightProperties& PropDigest = DigestedProperties[SightQuery->ObserverId]; + const AActor* ListenerBodyActor = ListenerPtr->GetBodyActor(); + float StimulusStrength = DefaultStimulusStrength; + FVector SeenLocation(0.f); + int32 NumberOfLoSChecksPerformed = 0; + int32 NumberOfAsyncLosCheckRequested = 0; + + const UAISense_Sight::EVisibilityResult VisibilityResult = ComputeVisibility(World, *SightQuery, Listener, ListenerBodyActor, Target, TargetActor, PropDigest, StimulusStrength, SeenLocation, NumberOfLoSChecksPerformed, NumberOfAsyncLosCheckRequested); + + TracesCount += NumberOfLoSChecksPerformed; + AsyncTracesCount += NumberOfAsyncLosCheckRequested; + + if (VisibilityResult == UAISense_Sight::EVisibilityResult::Pending) + { + QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::MoveToPending, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex)); + } + else + { + UE_CLOG(VisibilityResult != UAISense_Sight::EVisibilityResult::Visible && VisibilityResult != UAISense_Sight::EVisibilityResult::NotVisible, LogAIPerception, Error, TEXT("UAISense_Sight::Update received invalid Visibility result [%d] for query between Listener %s and Target %s. We'll consider it as NotVisible"), int(VisibilityResult), *GetNameSafe(ListenerBodyActor), *GetNameSafe(TargetActor)); + + const bool bIsVisible = VisibilityResult == UAISense_Sight::EVisibilityResult::Visible; + const bool bWasVisible = SightQuery->GetLastResult(); + + // Changed this up to support my VR Characters + const AVRBaseCharacter* VRChar = Cast(TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(); + + UpdateQueryVisibilityStatus(*SightQuery, Listener, bIsVisible, SeenLocation, StimulusStrength, *TargetActor, TargetLocation); + + const float SightRadiusSq = bWasVisible ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq; + SightQuery->Importance = CalcQueryImportance(Listener, TargetLocation, SightRadiusSq); + const bool bShouldBeInRange = SightQuery->Importance > 0.0f; + if (bIsInRangeQuery != bShouldBeInRange) + { + QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::SwapList, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex)); + } + + // restart query + SightQuery->OnProcessed(); + } + + + + // restart query + SightQuery->OnProcessed(); + } + else + { + + // put this index to "to be removed" array + QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::Remove, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex)); + if (TargetActor == nullptr) + { + InvalidTargets.AddUnique(SightQuery->TargetId); + } + } + } + NextOutOfRangeIndex = SightQueriesOutOfRange.Num() > 0 ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : 0; + +#if AISENSE_SIGHT_TIMESLICING_DEBUG + SlicingInfo.Stop(); + UE_LOG(LogAIPerception, VeryVerbose, TEXT("UAISense_Sight::Update processed %d sources %s [time slice limited? %d]"), NumQueriesProcessed, *SlicingInfo.ToString(), bHitTimeSliceLimit ? 1 : 0); +#else + UE_LOG(LogAIPerception, VeryVerbose, TEXT("UAISense_Sight::Update processed %d sources [time slice limited? %d]"), NumQueriesProcessed, bHitTimeSliceLimit ? 1 : 0); +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + + if (QueryOperations.Num() > 0) + { + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_QueryOperations); + + // Sort by InRange and by descending Index + QueryOperations.Sort([](const FQueryOperation& LHS, const FQueryOperation& RHS)->bool + { + if (LHS.bInRange != RHS.bInRange) + return LHS.bInRange; + return LHS.Index > RHS.Index; + }); + // Do all the removes first and save the out of range swaps because we will insert them at the right location to prevent sorting + TArray SightQueriesOutOfRangeToInsert; + for (FQueryOperation& Operation : QueryOperations) + { + switch (Operation.OpType) + { + case EOperationType::SwapList: + { + if (Operation.bInRange) + { + SightQueriesOutOfRangeToInsert.Push(SightQueriesInRange[Operation.Index]); + } + else + { + SightQueriesInRange.Add(SightQueriesOutOfRange[Operation.Index]); + } + }break; + + case EOperationType::MoveToPending: + { + SightQueriesPending.Add(Operation.bInRange ? SightQueriesInRange[Operation.Index] : SightQueriesOutOfRange[Operation.Index]); + }break; + + case EOperationType::Remove: + break; + + default: + check(false); + break; + } + + if (Operation.bInRange) + { + // In range queries are always sorted at the beginning of the update + SightQueriesInRange.RemoveAtSwap(Operation.Index, 1, /*bAllowShrinking*/false); + } + else + { + // Preserve the list ordered + SightQueriesOutOfRange.RemoveAt(Operation.Index, 1, /*bAllowShrinking*/false); + if (Operation.Index < NextOutOfRangeIndex) + { + NextOutOfRangeIndex--; + } + } + } + // Reinsert the saved out of range swaps + if (SightQueriesOutOfRangeToInsert.Num() > 0) + { + SightQueriesOutOfRange.Insert(SightQueriesOutOfRangeToInsert.GetData(), SightQueriesOutOfRangeToInsert.Num(), NextOutOfRangeIndex); + NextOutOfRangeIndex += SightQueriesOutOfRangeToInsert.Num(); + } + + if (InvalidTargets.Num() > 0) + { + // this should not be happening since UAIPerceptionSystem::OnPerceptionStimuliSourceEndPlay introduction + UE_VLOG(GetPerceptionSystem(), LogAIPerceptionVR, Error, TEXT("Invalid sight targets found during UAISense_Sight_VR::Update call")); + + for (const auto& TargetId : InvalidTargets) + { + // remove affected queries + RemoveAllQueriesToTarget(TargetId); + // remove target itself + ObservedTargets.Remove(TargetId); + } + + // remove holes + ObservedTargets.Compact(); + } + } + + //return SightQueryQueue.Num() > 0 ? 1.f/6 : FLT_MAX; + return 0.f; +} + +UAISense_Sight::EVisibilityResult UAISense_Sight_VR::ComputeVisibility(UWorld* World, FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const AActor* ListenerActor, FAISightTargetVR& Target, AActor* TargetActor, const FDigestedSightProperties& PropDigest, float& OutStimulusStrength, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested) const +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ComputeVisibility); + + // @Note that automagical "seeing" does not care about sight range nor vision cone + if (ShouldAutomaticallySeeTarget(PropDigest, &SightQuery, Listener, TargetActor, OutStimulusStrength)) + { + OutSeenLocation = FAISystem::InvalidLocation; + return UAISense_Sight::EVisibilityResult::Visible; + } + + // Changed this up to support my VR Characters + const AVRBaseCharacter* VRChar = Cast(TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(); + + const float SightRadiusSq = SightQuery.GetLastResult() ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq; + if (!FAISystem::CheckIsTargetInSightCone(Listener.CachedLocation, Listener.CachedDirection, PropDigest.PeripheralVisionAngleCos, PropDigest.PointOfViewBackwardOffset, PropDigest.NearClippingRadiusSq, SightRadiusSq, TargetLocation)) + { + return UAISense_Sight::EVisibilityResult::NotVisible; + } + + if (Target.SightTargetInterface != nullptr) + { + const bool bWasVisible = SightQuery.GetLastResult(); + + FCanBeSeenFromContext Context; + + FAISightQuery NewQuery; + NewQuery.FrameInfo.bLastResult = SightQuery.FrameInfo.bLastResult; + NewQuery.FrameInfo.LastProcessedFrameNumber = SightQuery.FrameInfo.LastProcessedFrameNumber; + NewQuery.Importance = SightQuery.Importance; + NewQuery.LastSeenLocation = SightQuery.LastSeenLocation; + NewQuery.ObserverId = SightQuery.ObserverId; + NewQuery.UserData = SightQuery.UserData; + NewQuery.Score = SightQuery.Score; + + NewQuery.TraceInfo.bLastResult = SightQuery.TraceInfo.bLastResult; + NewQuery.TraceInfo.FrameNumber = SightQuery.TraceInfo.FrameNumber; + NewQuery.TraceInfo.Index = SightQuery.TraceInfo.Index; + + /*FAISightTarget NewTarget; + NewTarget.InvalidTargetId = SightQuery.TargetId.InvalidTargetId; + NewTarget.SightTargetInterface = SightQuery.TargetId.SightTargetInterface; + NewTarget.Target = SightQuery.TargetId.Target; + NewTarget.TargetId = SightQuery.TargetId.TargetId; + NewTarget.TeamId = SightQuery.TargetId.TeamId;*/ + + NewQuery.TargetId = SightQuery.TargetId; + + Context.SightQueryID = NewQuery;//FAISightQueryIDVR(SightQuery); + Context.ObserverLocation = Listener.CachedLocation; + Context.IgnoreActor = ListenerActor; + Context.bWasVisible = &bWasVisible; + + const UAISense_Sight::EVisibilityResult Result = Target.SightTargetInterface->CanBeSeenFrom(Context, OutSeenLocation, OutNumberOfLoSChecksPerformed, OutNumberOfAsyncLosCheckRequested, OutStimulusStrength, &SightQuery.UserData, &OnPendingCanBeSeenQueryProcessedDelegate); + if (Result == UAISense_Sight::EVisibilityResult::Pending) + { + // we need to clear the trace info value in order to avoid interfering with the engine processed asynchronous queries + SightQuery.SetTraceInfo(FTraceHandle()); + } + return Result; + } + else + { + // we need to do tests ourselves + + const FCollisionQueryParams QueryParams = FCollisionQueryParams(SCENE_QUERY_STAT(AILineOfSight), true, ListenerActor); + + if (bUseAsynchronousTraceForDefaultSightQueries) + { + const FTraceHandle TraceHandle = World->AsyncLineTraceByChannel(EAsyncTraceType::Single, Listener.CachedLocation, TargetLocation, DefaultSightCollisionChannel, QueryParams, FCollisionResponseParams::DefaultResponseParam, &OnPendingTraceQueryProcessedDelegate); + if (!TraceHandle.IsValid()) + { + return UAISense_Sight::EVisibilityResult::NotVisible; + } + + ++OutNumberOfAsyncLosCheckRequested; + + // store the trace handle information here so that we can identify the associated query when we'll receive the delegate callback + SightQuery.SetTraceInfo(TraceHandle); + return UAISense_Sight::EVisibilityResult::Pending; + } + else + { + FHitResult HitResult; + const bool bHit = World->LineTraceSingleByChannel(HitResult, Listener.CachedLocation, TargetLocation, DefaultSightCollisionChannel, QueryParams, FCollisionResponseParams::DefaultResponseParam); + + ++OutNumberOfLoSChecksPerformed; + + if (UE::AISense_SightVR::IsTraceConsideredVisible(bHit ? &HitResult : nullptr, TargetActor)) + { + OutSeenLocation = TargetLocation; + return UAISense_Sight::EVisibilityResult::Visible; + } + else + { + return UAISense_Sight::EVisibilityResult::NotVisible; + } + } + } +} + +void UAISense_Sight_VR::UpdateQueryVisibilityStatus(FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor* TargetActor, const FVector& TargetLocation) const +{ + if (TargetActor) + { + UpdateQueryVisibilityStatus(SightQuery, Listener, bIsVisible, SeenLocation, StimulusStrength, *TargetActor, TargetLocation); + } +} + +void UAISense_Sight_VR::UpdateQueryVisibilityStatus(FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor& TargetActor, const FVector& TargetLocation) const +{ + if (bIsVisible) + { + const bool bHasValidSeenLocation = SeenLocation != FAISystem::InvalidLocation; + Listener.RegisterStimulus(&TargetActor, FAIStimulus(*this, StimulusStrength, bHasValidSeenLocation ? SeenLocation : SightQuery.LastSeenLocation, Listener.CachedLocation)); + SightQuery.SetLastResult(true); + if (bHasValidSeenLocation) + { + SightQuery.LastSeenLocation = SeenLocation; + } + } + // communicate failure only if we've seen given actor before + else if (SightQuery.GetLastResult()) + { + Listener.RegisterStimulus(&TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + SightQuery.SetLastResult(false); + SightQuery.LastSeenLocation = FAISystem::InvalidLocation; + } + + // SIGHT_LOG_SEGMENT(Listener.GetBodyActor(), Listener.CachedLocation, TargetLocation, bIsVisible ? FColor::Green : FColor::Red, TEXT("Target: %s"), *TargetActor.GetName()); +} + +void UAISense_Sight_VR::OnPendingCanBeSeenQueryProcessed(const FAISightQueryID& QueryID, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ProcessPendingQuery); + + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + + FAISightQueryIDVR NewID; + NewID.ObserverId = QueryID.ObserverId; + NewID.TargetId = QueryID.TargetId; + + + const int32 QueryIdx = SightQueriesPending.IndexOfByPredicate([&NewID](const FAISightQueryVR& Element) + { + return Element.ObserverId == NewID.ObserverId + && Element.TargetId == NewID.TargetId; + }); + + if (QueryIdx == INDEX_NONE) + { + // the query is not pending. It must have been removed because the source or the target have been removed + return; + } + + OnPendingQueryProcessed(QueryIdx, bIsVisible, StimulusStrength, SeenLocation, UserData); +} + +void UAISense_Sight_VR::OnPendingTraceQueryProcessed(const FTraceHandle& TraceHandle, FTraceDatum& TraceDatum) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ProcessPendingQuery); + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + const int32 QueryIdx = SightQueriesPending.IndexOfByPredicate([&TraceHandle](const FAISightQueryVR& Element) + { + return Element.TraceInfo.FrameNumber == TraceHandle._Data.FrameNumber + && Element.TraceInfo.Index == TraceHandle._Data.Index; + }); + + if (QueryIdx == INDEX_NONE) + { + // the query is not pending. It must have been removed because the source or the target have been removed + return; + } + + AActor* TargetActor = nullptr; + if (const FAISightTargetVR* Target = ObservedTargets.Find(SightQueriesPending[QueryIdx].TargetId)) + { + TargetActor = Target->Target.Get(); + } + const bool bIsVisible = UE::AISense_SightVR::IsTraceConsideredVisible(TraceDatum.OutHits.Num() > 0 ? &TraceDatum.OutHits[0] : nullptr, TargetActor); + + OnPendingQueryProcessed(QueryIdx, bIsVisible, DefaultStimulusStrength, TraceDatum.End, NullOpt, TargetActor); +} + +void UAISense_Sight_VR::OnPendingQueryProcessed(const int32 SightQueryIndex, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData, const TOptional InTargetActor) +{ + FAISightQueryVR SightQuery = SightQueriesPending[SightQueryIndex]; + SightQueriesPending.RemoveAtSwap(SightQueryIndex, 1, false); + + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + FPerceptionListener* Listener = ListenersMap.Find(SightQuery.ObserverId); + if (Listener == nullptr) + { + return; + } + + AActor* TargetActor = nullptr; + if (InTargetActor.IsSet()) + { + TargetActor = InTargetActor.GetValue(); + } + else + { + const FAISightTargetVR* Target = ObservedTargets.Find(SightQuery.TargetId); + TargetActor = Target ? Target->Target.Get() : nullptr; + } + + if (TargetActor == nullptr) + { + return; + } + + const bool bWasVisible = SightQuery.GetLastResult(); + + // Changed this up to support my VR Characters + const AVRBaseCharacter* VRChar = Cast(TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(); + + UpdateQueryVisibilityStatus(SightQuery, *Listener, bIsVisible, SeenLocation, StimulusStrength, *TargetActor, TargetLocation); + + if (UserData.IsSet()) + { + SightQuery.UserData = UserData.GetValue(); + } + + // Call this to be able to have an accurate tick time + SightQuery.OnProcessed(); + + const FDigestedSightProperties& PropDigest = DigestedProperties[SightQuery.ObserverId]; + const float SightRadiusSq = bWasVisible ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq; + SightQuery.Importance = CalcQueryImportance(*Listener, TargetLocation, SightRadiusSq); + const bool bShouldBeInRange = SightQuery.Importance > 0.0f; + if (bShouldBeInRange) + { + SightQueriesInRange.Add(SightQuery); + } + else + { + if (bSightQueriesOutOfRangeDirty) + { + SightQueriesOutOfRange.Add(SightQuery); + } + else + { + SightQueriesOutOfRange.Insert(SightQuery, NextOutOfRangeIndex); + ++NextOutOfRangeIndex; + } + } +} + +void UAISense_Sight_VR::RegisterEvent(const FAISightEventVR& Event) +{ + +} + +void UAISense_Sight_VR::RegisterSource(AActor& SourceActor) +{ + RegisterTarget(SourceActor); +} + +void UAISense_Sight_VR::UnregisterSource(AActor& SourceActor) +{ + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + const FAISightTargetVR::FTargetId AsTargetId = SourceActor.GetUniqueID(); + FAISightTargetVR AsTarget; + + + if (ObservedTargets.RemoveAndCopyValue(AsTargetId, AsTarget) + && (SightQueriesInRange.Num() + SightQueriesOutOfRange.Num() + SightQueriesPending.Num()) > 0) + { + AActor* TargetActor = AsTarget.Target.Get(); + + if (TargetActor) + { + // notify all interested observers that this source is no longer + // visible + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + auto RemoveQuery = [this, &ListenersMap, &AsTargetId, &TargetActor](TArray& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + FAISightQueryVR* SightQuery = &SightQueries[QueryIndex]; + if (SightQuery->TargetId == AsTargetId) + { + if (SightQuery->GetLastResult()) + { + FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId]; + ensure(Listener.Listener.IsValid()); + + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, SightQuery->LastSeenLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + } + + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + return EReverseForEachResult::Modified; + } + + return EReverseForEachResult::UnTouched; + }; + + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + bSightQueriesOutOfRangeDirty = true; + } + ReverseForEach(SightQueriesPending, RemoveQuery); + } + } +} + +bool UAISense_Sight_VR::RegisterTarget(AActor& TargetActor, const TFunction& OnAddedFunc /*= nullptr*/) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RegisterTarget); + + FAISightTargetVR* SightTarget = ObservedTargets.Find(TargetActor.GetUniqueID()); + + if (SightTarget != nullptr && SightTarget->GetTargetActor() != &TargetActor) + { + // this means given unique ID has already been recycled. + FAISightTargetVR NewSightTarget(&TargetActor); + + SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget)); + SightTarget->SightTargetInterface = Cast(&TargetActor); + } + else if (SightTarget == nullptr) + { + FAISightTargetVR NewSightTarget(&TargetActor); + + SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget)); + SightTarget->SightTargetInterface = Cast(&TargetActor); + } + + // set/update data + SightTarget->TeamId = FGenericTeamId::GetTeamIdentifier(&TargetActor); + + + // generate all pairs and add them to current Sight Queries + bool bNewQueriesAdded = false; + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast(&TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor.GetActorLocation(); + + for (AIPerception::FListenerMap::TConstIterator ItListener(ListenersMap); ItListener; ++ItListener) + { + const FPerceptionListener& Listener = ItListener->Value; + + if (!Listener.HasSense(GetSenseID()) || Listener.GetBodyActor() == &TargetActor) + { + continue; + } + + const FDigestedSightProperties& PropDigest = DigestedProperties[Listener.GetListenerID()]; + const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent(); + if (RegisterNewQuery(Listener, ListenersTeamAgent, TargetActor, SightTarget->TargetId, TargetLocation, PropDigest, OnAddedFunc)) + { + bNewQueriesAdded = true; + } + } + + // sort Sight Queries + if (bNewQueriesAdded) + { + RequestImmediateUpdate(); + } + + return bNewQueriesAdded; +} + +void UAISense_Sight_VR::OnNewListenerImpl(const FPerceptionListener& NewListener) +{ + UAIPerceptionComponent* NewListenerPtr = NewListener.Listener.Get(); + check(NewListenerPtr); + const UAISenseConfig_Sight_VR* SenseConfig = Cast(NewListenerPtr->GetSenseConfig(GetSenseID())); + + check(SenseConfig); + const FDigestedSightProperties PropertyDigest(*SenseConfig); + DigestedProperties.Add(NewListener.GetListenerID(), PropertyDigest); + + GenerateQueriesForListener(NewListener, PropertyDigest); +} + +void UAISense_Sight_VR::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction& OnAddedFunc/*= nullptr */) +{ + bool bNewQueriesAdded = false; + const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent(); + const AActor* Avatar = Listener.GetBodyActor(); + + // create sight queries with all legal targets + for (FTargetsContainer::TConstIterator ItTarget(ObservedTargets); ItTarget; ++ItTarget) + { + const AActor* TargetActor = ItTarget->Value.GetTargetActor(); + if (TargetActor == nullptr || TargetActor == Avatar) + { + continue; + } + + // Changed this up to support my VR Characters + const AVRBaseCharacter* VRChar = Cast(TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(); + if (RegisterNewQuery(Listener, ListenersTeamAgent, *TargetActor, ItTarget->Key, TargetLocation, PropertyDigest, OnAddedFunc)) + { + bNewQueriesAdded = true; + } + } + + // sort Sight Queries + if (bNewQueriesAdded) + { + RequestImmediateUpdate(); + } +} + +bool UAISense_Sight_VR::RegisterNewQuery(const FPerceptionListener& Listener, const IGenericTeamAgentInterface* ListenersTeamAgent, const AActor& TargetActor, const FAISightTargetVR::FTargetId& TargetId, const FVector& TargetLocation, const FDigestedSightProperties& PropDigest, const TFunction& OnAddedFunc) +{ + if (!FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, TargetActor, PropDigest.AffiliationFlags)) + { + return false; + } + + // create a sight query + const float Importance = CalcQueryImportance(Listener, TargetLocation, PropDigest.SightRadiusSq); + const bool bInRange = Importance > 0.0f; + if (!bInRange) + { + bSightQueriesOutOfRangeDirty = true; + } + + FAISightQueryVR& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef(); + AddedQuery.ObserverId = Listener.GetListenerID(); + AddedQuery.TargetId = TargetId; + AddedQuery.Importance = Importance; + + if (OnAddedFunc) + { + OnAddedFunc(AddedQuery); + } + + return true; +} + +void UAISense_Sight_VR::OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ListenerUpdate); + + // first, naive implementation: + // 1. remove all queries by this listener + // 2. proceed as if it was a new listener + + // see if this listener is a Target as well + const FAISightTargetVR::FTargetId AsTargetId = UpdatedListener.GetBodyActorUniqueID(); + FAISightTargetVR* AsTarget = ObservedTargets.Find(AsTargetId); + if (AsTarget != NULL) + { + if (AsTarget->Target.IsValid()) + { + // if still a valid target then backup list of observers for which the listener was visible to restore in the newly created queries + TSet LastVisibleObservers; + RemoveAllQueriesToTarget(AsTargetId, [&LastVisibleObservers](const FAISightQueryVR& Query) + { + if (Query.GetLastResult()) + { + LastVisibleObservers.Add(Query.ObserverId); + } + }); + + RegisterTarget(*(AsTarget->Target.Get()), [&LastVisibleObservers](FAISightQueryVR& Query) + { + Query.SetLastResult(LastVisibleObservers.Contains(Query.ObserverId)); + }); + } + else + { + RemoveAllQueriesToTarget(AsTargetId); + } + } + + const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID(); + + if (UpdatedListener.HasSense(GetSenseID())) + { + // if still a valid sense then backup list of targets that were visible by the listener to restore in the newly created queries + TSet LastVisibleTargets; + RemoveAllQueriesByListener(UpdatedListener, [&LastVisibleTargets](const FAISightQueryVR& Query) + { + if (Query.GetLastResult()) + { + LastVisibleTargets.Add(Query.TargetId); + } + }); + + const UAISenseConfig_Sight_VR* SenseConfig = Cast(UpdatedListener.Listener->GetSenseConfig(GetSenseID())); + check(SenseConfig); + + FDigestedSightProperties& PropertiesDigest = DigestedProperties.FindOrAdd(ListenerID); + PropertiesDigest = FDigestedSightProperties(*SenseConfig); + + GenerateQueriesForListener(UpdatedListener, PropertiesDigest, [&LastVisibleTargets](FAISightQueryVR & Query) + { + Query.SetLastResult(LastVisibleTargets.Contains(Query.TargetId)); + }); + } + else + { + // remove all queries + RemoveAllQueriesByListener(UpdatedListener); + DigestedProperties.Remove(ListenerID); + } +} + +void UAISense_Sight_VR::OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener) +{ + + bool bSkipListenerUpdate = false; + const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID(); + + + FDigestedSightProperties* PropertiesDigest = DigestedProperties.Find(ListenerID); + if (PropertiesDigest) + { + // The only parameter we need to rebuild all the queries for this listener is if the affiliation mask changed, otherwise there is nothing to update. + const UAISenseConfig_Sight_VR* SenseConfig = CastChecked(UpdatedListener.Listener->GetSenseConfig(GetSenseID())); + FDigestedSightProperties NewPropertiesDigest(*SenseConfig); + bSkipListenerUpdate = NewPropertiesDigest.AffiliationFlags == PropertiesDigest->AffiliationFlags; + *PropertiesDigest = NewPropertiesDigest; + } + + if (!bSkipListenerUpdate) + { + Super::OnListenerConfigUpdated(UpdatedListener); + } +} + +void UAISense_Sight_VR::OnListenerRemovedImpl(const FPerceptionListener& RemovedListener) +{ + + RemoveAllQueriesByListener(RemovedListener); + + DigestedProperties.FindAndRemoveChecked(RemovedListener.GetListenerID()); + + // note: there use to be code to remove all queries _to_ listener here as well + // but that was wrong - the fact that a listener gets unregistered doesn't have to + // mean it's being removed from the game altogether. +} + + +void UAISense_Sight_VR::RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction& OnRemoveFunc/*= nullptr */) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveByListener); + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + const uint32 ListenerId = Listener.GetListenerID(); + + auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + const FAISightQueryVR& SightQuery = SightQueries[QueryIndex]; + + if (SightQuery.ObserverId == ListenerId) + { + if (OnRemoveFunc) + { + OnRemoveFunc(SightQuery); + } + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + + return EReverseForEachResult::Modified; + } + + + + return EReverseForEachResult::UnTouched; + }; + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + + bSightQueriesOutOfRangeDirty = true; + } + ReverseForEach(SightQueriesPending, RemoveQuery); +} + +void UAISense_Sight_VR::RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, const TFunction& OnRemoveFunc/*= nullptr */) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveToTarget); + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + auto RemoveQuery = [&TargetId, &OnRemoveFunc](TArray& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + const FAISightQueryVR& SightQuery = SightQueries[QueryIndex]; + + if (SightQuery.TargetId == TargetId) + { + if (OnRemoveFunc) + { + OnRemoveFunc(SightQuery); + } + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + + return EReverseForEachResult::Modified; + } + + return EReverseForEachResult::UnTouched; + }; + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + + bSightQueriesOutOfRangeDirty = true; + } + ReverseForEach(SightQueriesPending, RemoveQuery); +} + + +void UAISense_Sight_VR::OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget) +{ + const uint32 ListenerId = Listener.GetListenerID(); + const uint32 TargetId = ActorToForget.GetUniqueID(); + + auto ForgetPreviousResult = [&ListenerId, &TargetId](FAISightQueryVR& SightQuery)->EForEachResult + { + if (SightQuery.ObserverId == ListenerId && SightQuery.TargetId == TargetId) + { + // assuming one query per observer-target pair + SightQuery.ForgetPreviousResult(); + return EForEachResult::Break; + } + return EForEachResult::Continue; + }; + + if (ForEach(SightQueriesInRange, ForgetPreviousResult) == EForEachResult::Continue) + { + if (ForEach(SightQueriesOutOfRange, ForgetPreviousResult) == EForEachResult::Continue) + { + ForEach(SightQueriesPending, ForgetPreviousResult); + } + } +} + +void UAISense_Sight_VR::OnListenerForgetsAll(const FPerceptionListener& Listener) +{ + UE_MT_SCOPED_WRITE_ACCESS(QueriesListAccessDetector); + + const uint32 ListenerId = Listener.GetListenerID(); + + auto ForgetPreviousResult = [&ListenerId](FAISightQueryVR& SightQuery)->EForEachResult + { + if (SightQuery.ObserverId == ListenerId) + { + SightQuery.ForgetPreviousResult(); + } + + return EForEachResult::Continue; + }; + + ForEach(SightQueriesInRange, ForgetPreviousResult); + ForEach(SightQueriesOutOfRange, ForgetPreviousResult); + ForEach(SightQueriesPending, ForgetPreviousResult); +} + + +//----------------------------------------------------------------------// +// +//----------------------------------------------------------------------// +UAISenseConfig_Sight_VR::UAISenseConfig_Sight_VR(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + DebugColor = FColor::Green; + AutoSuccessRangeFromLastSeenLocation = -1.0; + SightRadius = 3000.f; + LoseSightRadius = 3500.f; + PeripheralVisionAngleDegrees = 90; + DetectionByAffiliation.bDetectEnemies = true; + Implementation = UAISense_Sight_VR::StaticClass(); +} + +TSubclassOf UAISenseConfig_Sight_VR::GetSenseImplementation() const +{ + return *Implementation; +} + +#if WITH_GAMEPLAY_DEBUGGER +static FString DescribeColorHelper(const FColor& Color) +{ + int32 MaxColors = GColorList.GetColorsNum(); + for (int32 Idx = 0; Idx < MaxColors; Idx++) + { + if (Color == GColorList.GetFColorByIndex(Idx)) + { + return GColorList.GetColorNameByIndex(Idx); + } + } + + return FString(TEXT("color")); +} + +void UAISenseConfig_Sight_VR::DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const +{ + if (PerceptionComponent == nullptr || DebuggerCategory == nullptr) + { + return; + } + + FColor SightRangeColor = FColor::Green; + FColor LoseSightRangeColor = FColorList::NeonPink; + + // don't call Super implementation on purpose, replace color description line + DebuggerCategory->AddTextLine( + FString::Printf(TEXT("%s: {%s}%s {white}rangeIN:{%s}%s {white} rangeOUT:{%s}%s"), *GetSenseName(), + *GetDebugColor().ToString(), *DescribeColorHelper(GetDebugColor()), + *SightRangeColor.ToString(), *DescribeColorHelper(SightRangeColor), + *LoseSightRangeColor.ToString(), *DescribeColorHelper(LoseSightRangeColor)) + ); + + const AActor* BodyActor = PerceptionComponent->GetBodyActor(); + if (BodyActor != nullptr) + { + FVector BodyLocation, BodyFacing; + PerceptionComponent->GetLocationAndDirection(BodyLocation, BodyFacing); + + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, LoseSightRadius, 25.0f, LoseSightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, SightRadius, 25.0f, SightRangeColor)); + + const float SightPieLength = FMath::Max(LoseSightRadius, SightRadius); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing * SightPieLength), SightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(-PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor)); + } +} + +#if WITH_GAMEPLAY_DEBUGGER_MENU +void UAISense_Sight_VR::DescribeSelfToGameplayDebugger(const UAIPerceptionSystem& PerceptionSystem, FGameplayDebuggerCategory& DebuggerCategory) const +{ + const int32 TotalQueriesCount = SightQueriesInRange.Num() + SightQueriesOutOfRange.Num() + SightQueriesPending.Num(); + DebuggerCategory.AddTextLine( + FString::Printf(TEXT("%s: %d Targets, %d Queries (InRange:%d, OutOfRange:%d, Pending:%d)"), + *GetSenseID().Name.ToString(), + ObservedTargets.Num(), + TotalQueriesCount, + SightQueriesInRange.Num(), + SightQueriesOutOfRange.Num(), + SightQueriesPending.Num()) + ); +} +#endif // WITH_GAMEPLAY_DEBUGGER_MENU + +#endif // WITH_GAMEPLAY_DEBUGGER \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp new file mode 100644 index 0000000..61bc7ba --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp @@ -0,0 +1,383 @@ +#include "Misc/VREPhysicalAnimationComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VREPhysicalAnimationComponent) + +#include "SceneManagement.h" +#include "Components/SkeletalMeshComponent.h" +#include "PhysicsEngine/PhysicsAsset.h" +#include "PhysicsEngine/ConstraintInstance.h" +#include "ReferenceSkeleton.h" +#include "DrawDebugHelpers.h" + +#if ENABLE_DRAW_DEBUG +#include "Chaos/ImplicitObject.h" +#include "Chaos/TriangleMeshImplicitObject.h" +#endif + +#include "Physics/PhysicsInterfaceCore.h" +#include "Physics/PhysicsInterfaceTypes.h" + +UVREPhysicalAnimationComponent::UVREPhysicalAnimationComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bAutoSetPhysicsSleepSensitivity = true; + SleepThresholdMultiplier = 0.0f; +} + +/*void UVREPhysicalAnimationComponent::CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance) +{ + //UpdateWeldedBoneDriver(DeltaTime); +}*/ + +/*void UVREPhysicalAnimationComponent::OnWeldedMassUpdated(FBodyInstance* BodyInstance) +{ + // If our mass changed then our body was altered, lets re-init + SetupWeldedBoneDriver(true); +}*/ + +void UVREPhysicalAnimationComponent::SetWeldedBoneDriverPaused(bool bPaused) +{ + bIsPaused = bPaused; +} + +bool UVREPhysicalAnimationComponent::IsWeldedBoneDriverPaused() +{ + return bIsPaused; +} + +void UVREPhysicalAnimationComponent::RefreshWeldedBoneDriver() +{ + SetupWeldedBoneDriver_Implementation(true); +} + +void UVREPhysicalAnimationComponent::SetupWeldedBoneDriver(TArray BaseBoneNames) +{ + if (BaseBoneNames.Num()) + BaseWeldedBoneDriverNames = BaseBoneNames; + + SetupWeldedBoneDriver_Implementation(false); +} + +FTransform UVREPhysicalAnimationComponent::GetWorldSpaceRefBoneTransform(FReferenceSkeleton& RefSkel, int32 BoneIndex, int32 ParentBoneIndex) +{ + FTransform BoneTransform; + + if (BoneIndex > 0 && BoneIndex != ParentBoneIndex) + { + BoneTransform = RefSkel.GetRefBonePose()[BoneIndex]; + + FMeshBoneInfo BoneInfo = RefSkel.GetRefBoneInfo()[BoneIndex]; + if (BoneInfo.ParentIndex != 0 && BoneInfo.ParentIndex != ParentBoneIndex) + { + BoneTransform *= GetWorldSpaceRefBoneTransform(RefSkel, BoneInfo.ParentIndex, ParentBoneIndex); + } + } + + return BoneTransform; +} + +// #TODO: support off scaling +FTransform UVREPhysicalAnimationComponent::GetRefPoseBoneRelativeTransform(USkeletalMeshComponent* SkeleMesh, FName BoneName, FName ParentBoneName) +{ + FTransform BoneTransform; + + if (SkeleMesh && !BoneName.IsNone() && !ParentBoneName.IsNone()) + { + //SkelMesh->ClearRefPoseOverride(); + FReferenceSkeleton RefSkel; + RefSkel = SkeleMesh->GetSkinnedAsset()->GetRefSkeleton(); + + BoneTransform = GetWorldSpaceRefBoneTransform(RefSkel, RefSkel.FindBoneIndex(BoneName), RefSkel.FindBoneIndex(ParentBoneName)); + } + + return BoneTransform; +} + +void UVREPhysicalAnimationComponent::SetupWeldedBoneDriver_Implementation(bool bReInit) +{ + TArray OriginalData; + if (bReInit) + { + OriginalData = BoneDriverMap; + } + + BoneDriverMap.Empty(); + + USkeletalMeshComponent* SkeleMesh = GetSkeletalMesh(); + + if (!SkeleMesh || !SkeleMesh->Bodies.Num()) + return; + + // Get ref pose position and walk up the tree to the welded root to get our relative base pose. + //SkeleMesh->GetRefPosePosition() + + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if (PhysAsset && SkeleMesh->GetSkinnedAsset()) + { + + for (FName BaseWeldedBoneDriverName : BaseWeldedBoneDriverNames) + { + int32 ParentBodyIdx = PhysAsset->FindBodyIndex(BaseWeldedBoneDriverName); + + if (FBodyInstance* ParentBody = (ParentBodyIdx == INDEX_NONE ? nullptr : SkeleMesh->Bodies[ParentBodyIdx])) + { + // Build map of bodies that we want to control. + FPhysicsActorHandle& ActorHandle = ParentBody->WeldParent ? ParentBody->WeldParent->GetPhysicsActorHandle() : ParentBody->GetPhysicsActorHandle(); + + if (FPhysicsInterface::IsValid(ActorHandle) /*&& FPhysicsInterface::IsRigidBody(ActorHandle)*/) + { + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](FPhysicsActorHandle& Actor) + { + //TArray Shapes; + PhysicsInterfaceTypes::FInlineShapeArray Shapes; + FPhysicsInterface::GetAllShapes_AssumedLocked(Actor, Shapes); + + for (FPhysicsShapeHandle& Shape : Shapes) + { + if (ParentBody->WeldParent) + { + const FBodyInstance* OriginalBI = ParentBody->WeldParent->GetOriginalBodyInstance(Shape); + + if (OriginalBI != ParentBody) + { + // Not originally our shape + continue; + } + } + + FKShapeElem* ShapeElem = FChaosUserData::Get(FPhysicsInterface::GetUserData(Shape)); + if (ShapeElem) + { + FName TargetBoneName = ShapeElem->GetName(); + int32 BoneIdx = SkeleMesh->GetBoneIndex(TargetBoneName); + + if (BoneIdx != INDEX_NONE) + { + FWeldedBoneDriverData DriverData; + DriverData.BoneName = TargetBoneName; + //DriverData.ShapeHandle = Shape; + + if (bReInit && OriginalData.Num() - 1 >= BoneDriverMap.Num()) + { + DriverData.RelativeTransform = OriginalData[BoneDriverMap.Num()].RelativeTransform; + } + else + { + FTransform BoneTransform = FTransform::Identity; + if (SkeleMesh->GetBoneIndex(TargetBoneName) != INDEX_NONE) + BoneTransform = GetRefPoseBoneRelativeTransform(SkeleMesh, TargetBoneName, BaseWeldedBoneDriverName).Inverse(); + + //FTransform BoneTransform = SkeleMesh->GetSocketTransform(TargetBoneName, ERelativeTransformSpace::RTS_World); + + // Calc shape global pose + //FTransform RelativeTM = FPhysicsInterface::GetLocalTransform(Shape) * FPhysicsInterface::GetGlobalPose_AssumesLocked(ActorHandle); + + //RelativeTM = RelativeTM * BoneTransform.Inverse(); + //BoneTransform.SetScale3D(FVector(1.0f)); + DriverData.RelativeTransform = FPhysicsInterface::GetLocalTransform(Shape) * BoneTransform; + } + + BoneDriverMap.Add(DriverData); + } + } + } + + if (bAutoSetPhysicsSleepSensitivity && !ParentBody->WeldParent && BoneDriverMap.Num() > 0) + { + ParentBody->SleepFamily = ESleepFamily::Custom; + ParentBody->CustomSleepThresholdMultiplier = SleepThresholdMultiplier; + float SleepEnergyThresh = FPhysicsInterface::GetSleepEnergyThreshold_AssumesLocked(Actor); + SleepEnergyThresh *= ParentBody->GetSleepThresholdMultiplier(); + FPhysicsInterface::SetSleepEnergyThreshold_AssumesLocked(Actor, SleepEnergyThresh); + } + }); + } + } + } + } +} + +void UVREPhysicalAnimationComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Make sure base physical animation component runs its logic + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateWeldedBoneDriver(DeltaTime); +} + +void UVREPhysicalAnimationComponent::UpdateWeldedBoneDriver(float DeltaTime) +{ + if (!BoneDriverMap.Num()) + return; + + USkeletalMeshComponent* SkeleMesh = GetSkeletalMesh(); + + if (!SkeleMesh || !SkeleMesh->Bodies.Num())// || (!SkeleMesh->IsSimulatingPhysics(BaseWeldedBoneDriverNames) && !SkeleMesh->IsWelded())) + return; + + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if(PhysAsset && SkeleMesh->GetSkinnedAsset()) + { + for (FName BaseWeldedBoneDriverName : BaseWeldedBoneDriverNames) + { + int32 ParentBodyIdx = PhysAsset->FindBodyIndex(BaseWeldedBoneDriverName); + + if (FBodyInstance* ParentBody = (ParentBodyIdx == INDEX_NONE ? nullptr : SkeleMesh->Bodies[ParentBodyIdx])) + { + // Allow it to run even when not simulating physics, if we have a welded root then it needs to animate anyway + //if (!ParentBody->IsInstanceSimulatingPhysics() && !ParentBody->WeldParent) + // return; + + FPhysicsActorHandle& ActorHandle = ParentBody->WeldParent ? ParentBody->WeldParent->GetPhysicsActorHandle() : ParentBody->GetPhysicsActorHandle(); + + if (FPhysicsInterface::IsValid(ActorHandle) /*&& FPhysicsInterface::IsRigidBody(ActorHandle)*/) + { + +#if ENABLE_DRAW_DEBUG + if (bDebugDrawCollision) + { + Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, true); // Need to deactivate this later as well + Chaos::FDebugDrawQueue::GetInstance().SetMaxCost(20000); + //Chaos::FDebugDrawQueue::GetInstance().SetRegionOfInterest(SkeleMesh->GetComponentLocation(), 1000.0f); + Chaos::FDebugDrawQueue::GetInstance().SetEnabled(true); + } +#endif + + bool bModifiedBody = false; + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](FPhysicsActorHandle& Actor) + { + PhysicsInterfaceTypes::FInlineShapeArray Shapes; + FPhysicsInterface::GetAllShapes_AssumedLocked(Actor, Shapes); + + FTransform GlobalPose = FPhysicsInterface::GetGlobalPose_AssumesLocked(ActorHandle); + FTransform GlobalPoseInv = GlobalPose.Inverse(); + + +#if ENABLE_DRAW_DEBUG + if (bDebugDrawCollision) + { + Chaos::FDebugDrawQueue::GetInstance().SetRegionOfInterest(GlobalPose.GetLocation(), 100.0f); + } + +#endif + + for (FPhysicsShapeHandle& Shape : Shapes) + { + if (ParentBody->WeldParent) + { + const FBodyInstance* OriginalBI = ParentBody->WeldParent->GetOriginalBodyInstance(Shape); + + if (OriginalBI != ParentBody) + { + // Not originally our shape + continue; + } + } + + // Log the shapes name to match to the bone + FName TargetBoneName = NAME_None; + if (FKShapeElem* ShapeElem = FChaosUserData::Get(FPhysicsInterface::GetUserData(Shape))) + { + TargetBoneName = ShapeElem->GetName(); + } + else + { + // Cant find the matching shape + continue; + } + + if (FWeldedBoneDriverData* WeldedData = BoneDriverMap.FindByKey(TargetBoneName/*Shape*/)) + { + bModifiedBody = true; + + FTransform Trans = SkeleMesh->GetSocketTransform(WeldedData->BoneName, ERelativeTransformSpace::RTS_World); + + // This fixes a bug with simulating inverse scaled meshes + //Trans.SetScale3D(FVector(1.f) * Trans.GetScale3D().GetSignVector()); + FTransform GlobalTransform = WeldedData->RelativeTransform * Trans; + FTransform RelativeTM = GlobalTransform * GlobalPoseInv; + + if (!WeldedData->LastLocal.Equals(RelativeTM)) + { + FPhysicsInterface::SetLocalTransform(Shape, RelativeTM); + WeldedData->LastLocal = RelativeTM; + } + } + +#if ENABLE_DRAW_DEBUG + if (bDebugDrawCollision) + { + const Chaos::FImplicitObject* ShapeImplicit = Shape.Shape->GetGeometry().Get(); + Chaos::EImplicitObjectType Type = ShapeImplicit->GetType(); + + FTransform shapeTransform = FPhysicsInterface::GetLocalTransform(Shape); + FTransform FinalTransform = shapeTransform * GlobalPose; + Chaos::FRigidTransform3 RigTransform(FinalTransform); + Chaos::DebugDraw::DrawShape(RigTransform, ShapeImplicit, Chaos::FShapeOrShapesArray(), FColor::White); + } +#endif + } + }); + +#if ENABLE_DRAW_DEBUG + if (bDebugDrawCollision) + { + // Get the latest commands + TArray DrawCommands; + Chaos::FDebugDrawQueue::GetInstance().ExtractAllElements(DrawCommands); + if (DrawCommands.Num()) + { + DebugDrawMesh(DrawCommands); + } + Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, false); // Need to deactivate this later as well + Chaos::FDebugDrawQueue::GetInstance().SetEnabled(false); + } +#endif + } + + } + } + } +} + +#if ENABLE_DRAW_DEBUG +void UVREPhysicalAnimationComponent::DebugDrawMesh(const TArray& DrawCommands) +{ + UWorld* World = this->GetWorld(); + + // Draw all the captured elements in the viewport + for (const Chaos::FLatentDrawCommand& Command : DrawCommands) + { + switch (Command.Type) + { + case Chaos::FLatentDrawCommand::EDrawType::Point: + DrawDebugPoint(World, Command.LineStart, Command.Thickness, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority); + break; + case Chaos::FLatentDrawCommand::EDrawType::Line: + DrawDebugLine(World, Command.LineStart, Command.LineEnd, Command.Color, Command.bPersistentLines, -1.0f, 0.f, 0.f); + break; + case Chaos::FLatentDrawCommand::EDrawType::DirectionalArrow: + DrawDebugDirectionalArrow(World, Command.LineStart, Command.LineEnd, Command.ArrowSize, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority, Command.Thickness); + break; + case Chaos::FLatentDrawCommand::EDrawType::Sphere: + DrawDebugSphere(World, Command.LineStart, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority, Command.Thickness); + break; + case Chaos::FLatentDrawCommand::EDrawType::Box: + DrawDebugBox(World, Command.Center, Command.Extent, Command.Rotation, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority, Command.Thickness); + break; + case Chaos::FLatentDrawCommand::EDrawType::String: + DrawDebugString(World, Command.TextLocation, Command.Text, Command.TestBaseActor, Command.Color, -1.f, Command.bDrawShadow, Command.FontScale); + break; + case Chaos::FLatentDrawCommand::EDrawType::Circle: + { + FMatrix M = FRotationMatrix::MakeFromYZ(Command.YAxis, Command.ZAxis); + M.SetOrigin(Command.Center); + DrawDebugCircle(World, M, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority, Command.Thickness, Command.bDrawAxis); + break; + } + case Chaos::FLatentDrawCommand::EDrawType::Capsule: + DrawDebugCapsule(World, Command.Center, Command.HalfHeight, Command.Radius, Command.Rotation, Command.Color, Command.bPersistentLines, -1.f, Command.DepthPriority, Command.Thickness); + default: + break; + } + } +} +#endif \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp new file mode 100644 index 0000000..88ba0ba --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp @@ -0,0 +1,1251 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRFullScreenUserWidget.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRFullScreenUserWidget) + +//#include "Components/PostProcessComponent.h" +//#include "Engine/Engine.h" +#include "Engine/GameInstance.h" +#include "Engine/GameViewportClient.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/UserInterfaceSettings.h" +#include "GameFramework/WorldSettings.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "RenderingThread.h" +#include "RHI.h" +#include "UObject/ConstructorHelpers.h" +#include "UObject/Package.h" +#include "HAL/PlatformApplicationMisc.h" + +#include "Framework/Application/SlateApplication.h" +#include "Input/HittestGrid.h" +#include "Layout/Visibility.h" +#include "Slate/SceneViewport.h" +#include "Slate/WidgetRenderer.h" +#include "Widgets/Layout/SConstraintCanvas.h" +#include "Widgets/Layout/SDPIScaler.h" +#include "Widgets/SViewport.h" + +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" + +#if WITH_EDITOR +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" +#include "SLevelViewport.h" +#endif + +#define LOCTEXT_NAMESPACE "VRFullScreenUserWidget" + +///////////////////////////////////////////////////// +// Internal helper +namespace +{ + const FName NAME_LevelEditorName = "LevelEditor"; + //const FName NAME_SlateUI = "SlateUI"; + //const FName NAME_TintColorAndOpacity = "TintColorAndOpacity"; + //const FName NAME_OpacityFromTexture = "OpacityFromTexture"; + + EVisibility ConvertWindowVisibilityToVisibility(EWindowVisibility visibility) + { + switch (visibility) + { + case EWindowVisibility::Visible: + return EVisibility::Visible; + case EWindowVisibility::SelfHitTestInvisible: + return EVisibility::SelfHitTestInvisible; + default: + checkNoEntry(); + return EVisibility::SelfHitTestInvisible; + } + } + + namespace VPVRFullScreenUserWidgetPrivate + { + /** + * Class made to handle world cleanup and hide/cleanup active UserWidget to avoid touching public headers + */ + class FWorldCleanupListener + { + public: + + static FWorldCleanupListener* Get() + { + static FWorldCleanupListener Instance; + return &Instance; + } + + /** Disallow Copying / Moving */ + UE_NONCOPYABLE(FWorldCleanupListener); + + ~FWorldCleanupListener() + { + FWorldDelegates::OnWorldCleanup.RemoveAll(this); + } + + void AddWidget(UVRFullScreenUserWidget* InWidget) + { + WidgetsToHide.AddUnique(InWidget); + } + + void RemoveWidget(UVRFullScreenUserWidget* InWidget) + { + WidgetsToHide.RemoveSingleSwap(InWidget, false); + } + + private: + + FWorldCleanupListener() + { + FWorldDelegates::OnWorldCleanup.AddRaw(this, &FWorldCleanupListener::OnWorldCleanup); + } + + void OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources) + { + for (auto WeakWidgetIter = WidgetsToHide.CreateIterator(); WeakWidgetIter; ++WeakWidgetIter) + { + TWeakObjectPtr& WeakWidget = *WeakWidgetIter; + if (UVRFullScreenUserWidget* Widget = WeakWidget.Get()) + { + if (Widget->IsDisplayed() + && Widget->GetWidget() + && (Widget->GetWidget()->GetWorld() == InWorld)) + { + //Remove first since Hide removes object from the list + WeakWidgetIter.RemoveCurrent(); + Widget->Hide(); + } + } + else + { + WeakWidgetIter.RemoveCurrent(); + } + } + } + + private: + + TArray> WidgetsToHide; + }; + } +} + + +///////////////////////////////////////////////////// +// FVRWidgetPostProcessHitTester +class FVRWidgetPostProcessHitTester : public ICustomHitTestPath +{ +public: + FVRWidgetPostProcessHitTester(UWorld* InWorld, TSharedPtr InSlateWindow, TAttribute GetDPIAttribute) + : World(InWorld) + , VirtualSlateWindow(InSlateWindow) + , GetDPIAttribute(MoveTemp(GetDPIAttribute)) + , WidgetDrawSize(FIntPoint::ZeroValue) + , LastLocalHitLocation(FVector2D::ZeroVector) + {} + + virtual TArray GetBubblePathAndVirtualCursors(const FGeometry& InGeometry, FVector2D DesktopSpaceCoordinate, bool bIgnoreEnabledStatus) const override + { + // Get the list of widget at the requested location. + TArray ArrangedWidgets; + if (TSharedPtr SlateWindowPin = VirtualSlateWindow.Pin()) + { + // For some reason the DPI is not applied correctly so we need to multiply the window's native DPI ourselves. + // This is the setting you'd find in Windows Settings > Display > Scale and layout + // If this bit is skipped, then hovering widgets towards the bottom right will not work + // if system scale is > 100% AND the viewport size is not fixed (default). + const float DPI = GetDPIAttribute.Get(); + const FVector2D LocalMouseCoordinate = DPI * InGeometry.AbsoluteToLocal(DesktopSpaceCoordinate); + + constexpr float CursorRadius = 0.f; + ArrangedWidgets = SlateWindowPin->GetHittestGrid().GetBubblePath(LocalMouseCoordinate, CursorRadius, bIgnoreEnabledStatus); + + const FVirtualPointerPosition VirtualMouseCoordinate(LocalMouseCoordinate, LastLocalHitLocation); + LastLocalHitLocation = LocalMouseCoordinate; + + for (FWidgetAndPointer& ArrangedWidget : ArrangedWidgets) + { + ArrangedWidget.SetPointerPosition(VirtualMouseCoordinate); + } + } + + return ArrangedWidgets; + } + + virtual void ArrangeCustomHitTestChildren(FArrangedChildren& ArrangedChildren) const override + { + // Add the displayed slate to the list of widgets. + if (TSharedPtr SlateWindowPin = VirtualSlateWindow.Pin()) + { + FGeometry WidgetGeom; + ArrangedChildren.AddWidget(FArrangedWidget(SlateWindowPin.ToSharedRef(), WidgetGeom.MakeChild(WidgetDrawSize, FSlateLayoutTransform()))); + } + } + + virtual TOptional TranslateMouseCoordinateForCustomHitTestChild(const SWidget& ChildWidget, const FGeometry& MyGeometry, const FVector2D ScreenSpaceMouseCoordinate, const FVector2D LastScreenSpaceMouseCoordinate) const override + { + return TOptional(); + } + + void SetWidgetDrawSize(FIntPoint NewWidgetDrawSize) + { + WidgetDrawSize = NewWidgetDrawSize; + } + +private: + TWeakObjectPtr World; + TWeakPtr VirtualSlateWindow; + TAttribute GetDPIAttribute; + FIntPoint WidgetDrawSize; + mutable FVector2D LastLocalHitLocation; +}; + +///////////////////////////////////////////////////// +// FVRFullScreenUserWidget_Viewport + +bool FVRFullScreenUserWidget_Viewport::Display(UWorld* World, UUserWidget* Widget, TAttribute InDPIScale) +{ + const TSharedPtr FullScreenWidgetPinned = FullScreenCanvasWidget.Pin(); + if (Widget == nullptr || World == nullptr || FullScreenWidgetPinned.IsValid()) + { + return false; + } + + const TSharedRef FullScreenCanvas = SNew(SConstraintCanvas); + FullScreenCanvas->AddSlot() + .Offset(FMargin(0, 0, 0, 0)) + .Anchors(FAnchors(0, 0, 1, 1)) + .Alignment(FVector2D(0, 0)) + [ + SNew(SDPIScaler) + .DPIScale(MoveTemp(InDPIScale)) + [ + Widget->TakeWidget() + ] + ]; + + UGameViewportClient* ViewportClient = World->GetGameViewport(); + const bool bCanUseGameViewport = ViewportClient && World->IsGameWorld(); + if (bCanUseGameViewport) + { + FullScreenCanvasWidget = FullScreenCanvas; + ViewportClient->AddViewportWidgetContent(FullScreenCanvas); + return true; + } + +#if WITH_EDITOR + + const TSharedPtr PinnedTargetViewport = EditorTargetViewport.Pin(); + for (FLevelEditorViewportClient* Client : GEditor->GetLevelViewportClients()) + { + const TSharedPtr LevelViewport = StaticCastSharedPtr(Client->GetEditorViewportWidget()); + if (LevelViewport.IsValid() && LevelViewport->GetSceneViewport() == PinnedTargetViewport) + { + + LevelViewport->AddOverlayWidget(FullScreenCanvas); + FullScreenCanvasWidget = FullScreenCanvas; + OverlayWidgetLevelViewport = LevelViewport; + return true; + } + + } +#endif + return false; +} + +void FVRFullScreenUserWidget_Viewport::Hide(UWorld* World) +{ + TSharedPtr FullScreenWidgetPinned = FullScreenCanvasWidget.Pin(); + if (FullScreenWidgetPinned.IsValid()) + { + // Remove from Viewport and Fullscreen, in case the settings changed before we had the chance to hide. + UGameViewportClient* ViewportClient = World ? World->GetGameViewport() : nullptr; + if (ViewportClient) + { + ViewportClient->RemoveViewportWidgetContent(FullScreenWidgetPinned.ToSharedRef()); + } + +#if WITH_EDITOR + if (const TSharedPtr OverlayWidgetLevelViewportPinned = OverlayWidgetLevelViewport.Pin()) + { + OverlayWidgetLevelViewportPinned->RemoveOverlayWidget(FullScreenWidgetPinned.ToSharedRef()); + } + OverlayWidgetLevelViewport.Reset(); +#endif + + FullScreenCanvasWidget.Reset(); + } +} + +///////////////////////////////////////////////////// +// FVRFullScreenUserWidget_PostProcess + +FVRFullScreenUserWidget_PostProcess::FVRFullScreenUserWidget_PostProcess() + :// PostProcessMaterial(nullptr) + //, PostProcessTintColorAndOpacity(FLinearColor::White) + //, PostProcessOpacityFromTexture(1.0f) + bWidgetDrawSize(false) + , WidgetDrawSize(FIntPoint(640, 360)) + , bWindowFocusable(true) + , WindowVisibility(EWindowVisibility::SelfHitTestInvisible) + , bReceiveHardwareInput(true) + , RenderTargetBackgroundColor(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f)) + , RenderTargetBlendMode(EWidgetBlendMode::Masked) + , WidgetRenderTarget(nullptr) + //, PostProcessComponent(nullptr) + //, PostProcessMaterialInstance(nullptr) + , WidgetRenderer(nullptr) + , CurrentWidgetDrawSize(FIntPoint::ZeroValue) +{ + bRenderToTextureOnly = true; + bDrawToVRPreview = true; + VRDisplayType = ESpectatorScreenMode::TexturePlusEye; + PostVRDisplayType = ESpectatorScreenMode::SingleEye; +} + +bool FVRFullScreenUserWidget_PostProcess::Display(UWorld* World, UUserWidget* Widget, bool bInRenderToTextureOnly, TAttribute InDPIScale) +{ + TAttribute PostProcessDPIScale = 1.0f; + bool bOk = CreateRenderer(World, Widget, MoveTemp(PostProcessDPIScale)); + + if (bRenderToTextureOnly && IsValid(WidgetRenderTarget) && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (VRDisplayType == ESpectatorScreenMode::TexturePlusEye) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::TexturePlusEye); + } + + FSpectatorScreenModeTexturePlusEyeLayout Layout; + Layout.bClearBlack = true; + Layout.bDrawEyeFirst = true; + Layout.bUseAlpha = true; + Layout.EyeRectMin = FVector2D(0.f, 0.f); + Layout.EyeRectMax = FVector2D(1.f, 1.f); + Layout.TextureRectMin = FVector2D(0.f, 0.f); + Layout.TextureRectMax = FVector2D(1.f, 1.f); + Controller->SetSpectatorScreenModeTexturePlusEyeLayout(Layout); + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + else if (VRDisplayType == ESpectatorScreenMode::Texture) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::Texture); + } + + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + } + } + + if (!bRenderToTextureOnly) + { + //bOk &= CreatePostProcessComponent(World); + } + + return bOk; +} + +void FVRFullScreenUserWidget_PostProcess::Hide(UWorld* World) +{ + if (!bRenderToTextureOnly) + { + //ReleasePostProcessComponent(); + } + + if (bRenderToTextureOnly && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (Controller->GetSpectatorScreenMode() == ESpectatorScreenMode::TexturePlusEye || Controller->GetSpectatorScreenMode() == ESpectatorScreenMode::Texture) + { + Controller->SetSpectatorScreenMode(PostVRDisplayType); + Controller->SetSpectatorScreenTexture(nullptr); + } + } + } + + ReleaseRenderer(); +} + +void FVRFullScreenUserWidget_PostProcess::Tick(UWorld* World, float DeltaSeconds) +{ + TickRenderer(World, DeltaSeconds); +} + +TSharedPtr FVRFullScreenUserWidget_PostProcess::GetSlateWindow() const +{ + return SlateWindow; +} + +/*bool FVRFullScreenUserWidget_PostProcess::CreatePostProcessComponent(UWorld* World) +{ + ReleasePostProcessComponent(); + if (World && PostProcessMaterial) + { + AWorldSettings* WorldSetting = World->GetWorldSettings(); + PostProcessComponent = NewObject(WorldSetting, NAME_None, RF_Transient); + PostProcessComponent->bEnabled = true; + PostProcessComponent->bUnbound = true; + PostProcessComponent->RegisterComponent(); + + PostProcessMaterialInstance = UMaterialInstanceDynamic::Create(PostProcessMaterial, World); + + // set the parameter immediately + PostProcessMaterialInstance->SetTextureParameterValue(NAME_SlateUI, WidgetRenderTarget); + PostProcessMaterialInstance->SetVectorParameterValue(NAME_TintColorAndOpacity, PostProcessTintColorAndOpacity); + PostProcessMaterialInstance->SetScalarParameterValue(NAME_OpacityFromTexture, PostProcessOpacityFromTexture); + + PostProcessComponent->Settings.WeightedBlendables.Array.SetNumZeroed(1); + PostProcessComponent->Settings.WeightedBlendables.Array[0].Weight = 1.f; + PostProcessComponent->Settings.WeightedBlendables.Array[0].Object = PostProcessMaterialInstance; + } + + return PostProcessComponent && PostProcessMaterialInstance; +}*/ + +/*void FVRFullScreenUserWidget_PostProcess::ReleasePostProcessComponent() +{ + if (PostProcessComponent) + { + PostProcessComponent->UnregisterComponent(); + } + PostProcessComponent = nullptr; + PostProcessMaterialInstance = nullptr; +}*/ + +bool FVRFullScreenUserWidget_PostProcess::CreateRenderer(UWorld* World, UUserWidget* Widget, TAttribute InDPIScale) +{ + ReleaseRenderer(); + + if (World && Widget) + { + constexpr bool bApplyGammaCorrection = true; + WidgetRenderer = new FWidgetRenderer(bApplyGammaCorrection); + WidgetRenderer->SetIsPrepassNeeded(true); + + // CalculateWidgetDrawSize may sometimes return {0,0}, e.g. right after engine startup when viewport not yet initialized. + // TickRenderer will call Resize automatically once CurrentWidgetDrawSize is updated to be non-zero. + checkf(CurrentWidgetDrawSize == FIntPoint::ZeroValue, TEXT("Expected ReleaseRenderer to reset CurrentWidgetDrawSize.")); + SlateWindow = SNew(SVirtualWindow).Size(CurrentWidgetDrawSize); + SlateWindow->SetIsFocusable(bWindowFocusable); + SlateWindow->SetVisibility(ConvertWindowVisibilityToVisibility(WindowVisibility)); + SlateWindow->SetContent( + + SNew(SDPIScaler) + .DPIScale(InDPIScale) + [ + Widget->TakeWidget() + ] + ); + + RegisterHitTesterWithViewport(World); + + if (!Widget->IsDesignTime() && World->IsGameWorld()) + { + UGameInstance* GameInstance = World->GetGameInstance(); + UGameViewportClient* GameViewportClient = GameInstance ? GameInstance->GetGameViewportClient() : nullptr; + if (GameViewportClient) + { + SlateWindow->AssignParentWidget(GameViewportClient->GetGameViewportWidget()); + } + } + + FLinearColor ActualBackgroundColor = RenderTargetBackgroundColor; + switch (RenderTargetBlendMode) + { + case EWidgetBlendMode::Opaque: + ActualBackgroundColor.A = 1.0f; + break; + case EWidgetBlendMode::Masked: + ActualBackgroundColor.A = 0.0f; + break; + } + + // Skip InitCustomFormat call because CalculateWidgetDrawSize may sometimes return {0,0}, e.g. right after engine startup when viewport not yet initialized + // TickRenderer will call InitCustomFormat automatically once CurrentWidgetDrawSize is updated to be non-zero. + checkf(CurrentWidgetDrawSize == FIntPoint::ZeroValue, TEXT("Expected ReleaseRenderer to reset CurrentWidgetDrawSize.")); + // Outer needs to be transient package: otherwise we cause a world memory leak using "Save Current Level As" due to reference not getting replaced correctly + WidgetRenderTarget = NewObject(GetTransientPackage(), NAME_None, RF_Transient); + WidgetRenderTarget->ClearColor = ActualBackgroundColor; + } + + return WidgetRenderer && WidgetRenderTarget; +} + +void FVRFullScreenUserWidget_PostProcess::ReleaseRenderer() +{ + if (WidgetRenderer) + { + BeginCleanup(WidgetRenderer); + WidgetRenderer = nullptr; + } + UnRegisterHitTesterWithViewport(); + + SlateWindow.Reset(); + WidgetRenderTarget = nullptr; + CurrentWidgetDrawSize = FIntPoint::ZeroValue; +} + +void FVRFullScreenUserWidget_PostProcess::TickRenderer(UWorld* World, float DeltaSeconds) +{ + check(World); + if (IsValid(WidgetRenderTarget)) + { + + if (bRenderToTextureOnly && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::TexturePlusEye); + FSpectatorScreenModeTexturePlusEyeLayout Layout; + Layout.bClearBlack = true; + Layout.bDrawEyeFirst = true; + Layout.bUseAlpha = true; + Layout.EyeRectMin = FVector2D(0.f, 0.f); + Layout.EyeRectMax = FVector2D(1.f, 1.f); + Layout.TextureRectMin = FVector2D(0.f, 0.f); + Layout.TextureRectMax = FVector2D(1.f, 1.f); + Controller->SetSpectatorScreenModeTexturePlusEyeLayout(Layout); + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + } + } + + const float DrawScale = 1.0f; + + const FIntPoint NewCalculatedWidgetSize = CalculateWidgetDrawSize(World); + if (NewCalculatedWidgetSize != CurrentWidgetDrawSize) + { + if (IsTextureSizeValid(NewCalculatedWidgetSize)) + { + CurrentWidgetDrawSize = NewCalculatedWidgetSize; + WidgetRenderTarget->InitCustomFormat(CurrentWidgetDrawSize.X, CurrentWidgetDrawSize.Y, PF_B8G8R8A8, false); + WidgetRenderTarget->UpdateResourceImmediate(); + SlateWindow->Resize(CurrentWidgetDrawSize); + if (CustomHitTestPath) + { + CustomHitTestPath->SetWidgetDrawSize(CurrentWidgetDrawSize); + } + } + else + { + Hide(World); + } + } + + if (WidgetRenderer && CurrentWidgetDrawSize != FIntPoint::ZeroValue) + { + WidgetRenderer->DrawWindow( + WidgetRenderTarget, + SlateWindow->GetHittestGrid(), + SlateWindow.ToSharedRef(), + DrawScale, + CurrentWidgetDrawSize, + DeltaSeconds); + } + } +} + +FIntPoint FVRFullScreenUserWidget_PostProcess::CalculateWidgetDrawSize(UWorld* World) +{ + if (bWidgetDrawSize) + { + return WidgetDrawSize; + } + + if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE) + { + if (UGameViewportClient* ViewportClient = World->GetGameViewport()) + { + // The viewport maybe resizing or not yet initialized. + // See TickRenderer(), it will be resize on the next tick to the proper size. + // We initialized all the rendering with an small size. + + const float SmallWidgetSize = 16.f; + FVector2D OutSize = FVector2D(SmallWidgetSize, SmallWidgetSize); + OutSize = ViewportClient->GetWindow()->GetSizeInScreen(); //ViewportClient->GetViewportSize(OutSize); + if (OutSize.X < UE_SMALL_NUMBER) + { + OutSize = FVector2D(SmallWidgetSize, SmallWidgetSize); + } + return OutSize.IntPoint(); + } + + return FIntPoint::ZeroValue; + } +#if WITH_EDITOR + + if (const TSharedPtr SharedActiveViewport = EditorTargetViewport.Pin()) + { + return SharedActiveViewport->GetSize(); + } + //UE_LOG(LogVPUtilities, Warning, TEXT("CalculateWidgetDrawSize failed for editor world.")); +#endif + + return FIntPoint::ZeroValue; +} + +bool FVRFullScreenUserWidget_PostProcess::IsTextureSizeValid(FIntPoint Size) const +{ + const int32 MaxAllowedDrawSize = GetMax2DTextureDimension(); + return Size.X > 0 && Size.Y > 0 && Size.X <= MaxAllowedDrawSize && Size.Y <= MaxAllowedDrawSize; +} + +void FVRFullScreenUserWidget_PostProcess::RegisterHitTesterWithViewport(UWorld* World) +{ + if (!bReceiveHardwareInput && FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().RegisterVirtualWindow(SlateWindow.ToSharedRef()); + } + const TSharedPtr EngineViewportWidget = GetViewport(World); + if (EngineViewportWidget && bReceiveHardwareInput) + { + if (EngineViewportWidget->GetCustomHitTestPath()) + { + //UE_LOG(LogVPUtilities, Warning, TEXT("Can't register a hit tester for FullScreenUserWidget. There is already one defined.")); + } + else + { + ViewportWidget = EngineViewportWidget; + CustomHitTestPath = MakeShared(World, SlateWindow, TAttribute::CreateRaw(this, &FVRFullScreenUserWidget_PostProcess::GetDPIScaleForPostProcessHitTester, TWeakObjectPtr(World))); + CustomHitTestPath->SetWidgetDrawSize(CurrentWidgetDrawSize); + EngineViewportWidget->SetCustomHitTestPath(CustomHitTestPath); + } + } +} + +void FVRFullScreenUserWidget_PostProcess::UnRegisterHitTesterWithViewport() +{ + if (SlateWindow.IsValid() && FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().UnregisterVirtualWindow(SlateWindow.ToSharedRef()); + } + + if (TSharedPtr ViewportWidgetPin = ViewportWidget.Pin()) + { + if (ViewportWidgetPin->GetCustomHitTestPath() == CustomHitTestPath) + { + ViewportWidgetPin->SetCustomHitTestPath(nullptr); + } + } + + ViewportWidget.Reset(); + CustomHitTestPath.Reset(); +} + +TSharedPtr FVRFullScreenUserWidget_PostProcess::GetViewport(UWorld* World) const +{ + if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE) + { + return GEngine->GetGameViewportWidget(); + } + +#if WITH_EDITOR + if (const TSharedPtr TargetViewportPin = EditorTargetViewport.Pin()) + { + return TargetViewportPin->GetViewportWidget().Pin(); + } +#endif + + return nullptr; +} + +float FVRFullScreenUserWidget_PostProcess::GetDPIScaleForPostProcessHitTester(TWeakObjectPtr World) const +{ + FSceneViewport* Viewport = nullptr; + if (ensure(World.IsValid()) && World->IsGameWorld()) + { + UGameViewportClient* ViewportClient = World->GetGameViewport(); + Viewport = ensure(ViewportClient) ? ViewportClient->GetGameViewport() : nullptr; + } + +#if WITH_EDITOR + const TSharedPtr ViewportPin = EditorTargetViewport.Pin(); + Viewport = Viewport ? Viewport : ViewportPin.Get(); +#endif + + const bool bCanScale = Viewport && !Viewport->HasFixedSize(); + if (!bCanScale) + { + return 1.f; + } + + // For some reason the DPI is not applied correctly when the viewport has a fixed size and the system scale is > 100%. + // This is the setting you'd find in Windows Settings > Display > Scale and layout + // If this bit is skipped, then hovering widgets towards the bottom right will not work + // if system scale is > 100% AND the viewport size is not fixed (default). + const TSharedPtr ViewportWindow = Viewport->FindWindow(); + return ViewportWindow ? ViewportWindow->GetDPIScaleFactor() : 1.f; +} + + +///////////////////////////////////////////////////// +// UVRFullScreenUserWidget + +UVRFullScreenUserWidget::UVRFullScreenUserWidget(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurrentDisplayType(EVRWidgetDisplayType::Inactive) + , bDisplayRequested(false) +{ + //Material'/VRExpansionPlugin/Materials/VRWidgetPostProcessMaterial.WidgetPostProcessMaterial' + //static ConstructorHelpers::FObjectFinder PostProcessMaterial_Finder(TEXT("/VRExpansionPlugin/Materials/VRWidgetPostProcessMaterial")); + //PostProcessDisplayType.PostProcessMaterial = PostProcessMaterial_Finder.Object; +} + +void UVRFullScreenUserWidget::BeginDestroy() +{ + Hide(); + Super::BeginDestroy(); +} + +bool UVRFullScreenUserWidget::ShouldDisplay(UWorld* InWorld) const +{ +#if UE_SERVER + return false; +#else + if (GUsingNullRHI || HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject) || IsRunningDedicatedServer()) + { + return false; + } + + return GetDisplayType(InWorld) != EVRWidgetDisplayType::Inactive; +#endif //!UE_SERVER +} + +EVRWidgetDisplayType UVRFullScreenUserWidget::GetDisplayType(UWorld* InWorld) const +{ + if (InWorld) + { + if (InWorld->WorldType == EWorldType::Game) + { + return GameDisplayType; + } +#if WITH_EDITOR + else if (InWorld->WorldType == EWorldType::PIE) + { + return PIEDisplayType; + } + else if (InWorld->WorldType == EWorldType::Editor) + { + return EditorDisplayType; + } +#endif // WITH_EDITOR + } + return EVRWidgetDisplayType::Inactive; +} + +bool UVRFullScreenUserWidget::IsDisplayed() const +{ + return CurrentDisplayType != EVRWidgetDisplayType::Inactive; +} + +bool UVRFullScreenUserWidget::Display(UWorld* InWorld) +{ + bDisplayRequested = true; + World = InWorld; + +#if WITH_EDITOR + if (!EditorTargetViewport.IsValid() && !World->IsGameWorld()) + { + //UE_LOG(LogVPUtilities, Log, TEXT("No TargetViewport set. Defaulting to FLevelEditorModule::GetFirstActiveLevelViewport.")) + if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName)) + { + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(NAME_LevelEditorName); + const TSharedPtr ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport(); + EditorTargetViewport = ActiveLevelViewport ? ActiveLevelViewport->GetSharedActiveViewport() : nullptr; + } + + if (!EditorTargetViewport.IsValid()) + { + //UE_LOG(LogVPUtilities, Error, TEXT("FLevelEditorModule::GetFirstActiveLevelViewport found no level viewport. UVPFullScreenUserWidget will not display.")) + return false; + } + } + + // Make sure that each display type has also received the EditorTargetViewport + SetEditorTargetViewport(EditorTargetViewport); +#endif + + bool bWasAdded = false; + if (InWorld && WidgetClass && ShouldDisplay(InWorld) && CurrentDisplayType == EVRWidgetDisplayType::Inactive) + { + const bool bCreatedWidget = InitWidget(); + if (!bCreatedWidget) + { + //UE_LOG(LogVPUtilities, Error, TEXT("Failed to create subwidget for UVPFullScreenUserWidget.")); + return false; + } + + CurrentDisplayType = GetDisplayType(InWorld); + + TAttribute GetDpiScaleAttribute = TAttribute::CreateLambda([WeakThis = TWeakObjectPtr(this)]() + { + return WeakThis.IsValid() ? WeakThis->GetViewportDPIScale() : 1.f; + + }); + + if (CurrentDisplayType == EVRWidgetDisplayType::Viewport) + { + bWasAdded = ViewportDisplayType.Display(InWorld, Widget, MoveTemp(GetDpiScaleAttribute)); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /*|| (CurrentDisplayType == EVRWidgetDisplayType::Composure)*/) + { + bWasAdded = PostProcessDisplayType.Display(InWorld, Widget, /*(CurrentDisplayType == EVPWidgetDisplayType::Composure)*/true, MoveTemp(GetDpiScaleAttribute)); + } + + if (bWasAdded) + { + FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &UVRFullScreenUserWidget::OnLevelRemovedFromWorld); + FWorldDelegates::OnWorldCleanup.AddUObject(this, &UVRFullScreenUserWidget::OnWorldCleanup); + VPVRFullScreenUserWidgetPrivate::FWorldCleanupListener::Get()->AddWidget(this); + + // If we are using Composure as our output, then send the WidgetRenderTarget to each one + /*if (CurrentDisplayType == EVRWidgetDisplayType::Composure) + { + static const FString TextureCompClassName("BP_TextureRTCompElement_C"); + static const FName TextureInputPropertyName("TextureRTInput"); + + for (ACompositingElement* Layer : PostProcessDisplayType.ComposureLayerTargets) + { + if (Layer && (Layer->GetClass()->GetName() == TextureCompClassName)) + { + FProperty* TextureInputProperty = Layer->GetClass()->FindPropertyByName(TextureInputPropertyName); + if (TextureInputProperty) + { + FObjectProperty* TextureInputObjectProperty = CastField(TextureInputProperty); + if (TextureInputObjectProperty) + { + UTextureRenderTarget2D** DestTextureRT2D = TextureInputProperty->ContainerPtrToValuePtr(Layer); + if (DestTextureRT2D) + { + TextureInputObjectProperty->SetObjectPropertyValue(DestTextureRT2D, PostProcessDisplayType.WidgetRenderTarget); + Layer->RerunConstructionScripts(); + } + } + } + } + else if (Layer) + { + UE_LOG(LogVPUtilities, Warning, TEXT("VRFullScreenUserWidget - ComposureLayerTarget entry '%s' is not the correct class '%s'"), *Layer->GetName(), *TextureCompClassName); + } + } + }*/ + } + } + + return bWasAdded; +} + +void UVRFullScreenUserWidget::Hide() +{ + bDisplayRequested = false; + + if (CurrentDisplayType != EVRWidgetDisplayType::Inactive) + { + ReleaseWidget(); + + if (CurrentDisplayType == EVRWidgetDisplayType::Viewport) + { + ViewportDisplayType.Hide(World.Get()); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /*|| (CurrentDisplayType == EVRWidgetDisplayType::Composure)*/) + { + PostProcessDisplayType.Hide(World.Get()); + } + CurrentDisplayType = EVRWidgetDisplayType::Inactive; + } + + FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); + FWorldDelegates::OnWorldCleanup.RemoveAll(this); + VPVRFullScreenUserWidgetPrivate::FWorldCleanupListener::Get()->RemoveWidget(this); + World.Reset(); +} + +void UVRFullScreenUserWidget::Tick(float DeltaSeconds) +{ + if (CurrentDisplayType != EVRWidgetDisplayType::Inactive) + { + UWorld* CurrentWorld = World.Get(); + if (CurrentWorld == nullptr) + { + Hide(); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /* || (CurrentDisplayType == EVPWidgetDisplayType::Composure)*/) + { + PostProcessDisplayType.Tick(CurrentWorld, DeltaSeconds); + } + } +} + +void UVRFullScreenUserWidget::SetDisplayTypes(EVRWidgetDisplayType InEditorDisplayType, EVRWidgetDisplayType InGameDisplayType, EVRWidgetDisplayType InPIEDisplayType) +{ + EditorDisplayType = InEditorDisplayType; + GameDisplayType = InGameDisplayType; + PIEDisplayType = InPIEDisplayType; +} + +void UVRFullScreenUserWidget::SetOverrideWidget(UUserWidget* InWidget) +{ + if (ensureMsgf(!IsDisplayed(), TEXT("For simplicity of API you can only override the widget before displaying."))) + { + Widget = InWidget; + } +} + +#if WITH_EDITOR +void UVRFullScreenUserWidget::SetEditorTargetViewport(TWeakPtr InTargetViewport) +{ + EditorTargetViewport = InTargetViewport; + ViewportDisplayType.EditorTargetViewport = InTargetViewport; + PostProcessDisplayType.EditorTargetViewport = InTargetViewport; +} + +void UVRFullScreenUserWidget::ResetEditorTargetViewport() +{ + EditorTargetViewport.Reset(); + ViewportDisplayType.EditorTargetViewport.Reset(); + PostProcessDisplayType.EditorTargetViewport.Reset(); +} +#endif + +bool UVRFullScreenUserWidget::InitWidget() +{ + const bool bCanCreate = !Widget && WidgetClass && ensure(World.Get()) && FSlateApplication::IsInitialized(); + if (!bCanCreate) + { + return false; + } + + // Could fail e.g. if the class has been marked deprecated or abstract. + Widget = CreateWidget(World.Get(), WidgetClass); + //UE_CLOG(!Widget, LogVPUtilities, Warning, TEXT("Failed to create widget with class %s. Review the log for more info."), *WidgetClass->GetPathName()) + if (Widget) + { + Widget->SetFlags(RF_Transient); + } + + return Widget != nullptr; +} + +void UVRFullScreenUserWidget::ReleaseWidget() +{ + Widget = nullptr; +} + +void UVRFullScreenUserWidget::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) +{ + // If the InLevel is invalid, then the entire world is about to disappear. + //Hide the widget to clear the memory and reference to the world it may hold. + if (InLevel == nullptr && InWorld && InWorld == World.Get()) + { + Hide(); + } +} + +void UVRFullScreenUserWidget::OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources) +{ + + if (IsDisplayed() && World == InWorld) + { + Hide(); + } +} + +FVector2D UVRFullScreenUserWidget::FindSceneViewportSize() +{ + ensure(World.IsValid()); + + const UWorld* CurrentWorld = World.Get(); + const bool bIsPlayWorld = CurrentWorld && (CurrentWorld->WorldType == EWorldType::Game || CurrentWorld->WorldType == EWorldType::PIE); + if (bIsPlayWorld) + { + if (UGameViewportClient* ViewportClient = World->GetGameViewport()) + { + FVector2D OutSize; + ViewportClient->GetViewportSize(OutSize); + return OutSize; + } + } + +#if WITH_EDITOR + + if (const TSharedPtr TargetViewportPin = EditorTargetViewport.Pin()) + { + return TargetViewportPin->GetSize(); + } +#endif + + ensureMsgf(false, TEXT( + "FindSceneViewportSize failed. Likely Hide() was called (making World = nullptr) or EditorTargetViewport " + "reset externally (possibly as part of Hide()). After Hide() is called all widget code should stop calling " + "FindSceneViewportSize. Investigate whether something was not cleaned up correctly!" + ) + ); + return FVector2d::ZeroVector; +} + +float UVRFullScreenUserWidget::GetViewportDPIScale() +{ + float UIScale = 1.0f; + float PlatformScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); + + UWorld* CurrentWorld = World.Get(); + if ((CurrentDisplayType == EVRWidgetDisplayType::Viewport) && CurrentWorld && (CurrentWorld->WorldType == EWorldType::Game || CurrentWorld->WorldType == EWorldType::PIE)) + { + // If we are in Game or PIE in Viewport display mode, the GameLayerManager will scale correctly so just return the Platform Scale + UIScale = PlatformScale; + } + else + { + // Otherwise when in Editor mode, the editor automatically scales to the platform size, so we only care about the UI scale + const FIntPoint ViewportSize = FindSceneViewportSize().IntPoint(); + UIScale = GetDefault()->GetDPIScaleBasedOnSize(ViewportSize); + } + + return UIScale; +} + + +#if WITH_EDITOR + +void UVRFullScreenUserWidget::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + FProperty* Property = PropertyChangedEvent.MemberProperty; + + if (Property && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + static FName NAME_WidgetClass = GET_MEMBER_NAME_CHECKED(UVRFullScreenUserWidget, WidgetClass); + static FName NAME_EditorDisplayType = GET_MEMBER_NAME_CHECKED(UVRFullScreenUserWidget, EditorDisplayType); + //static FName NAME_PostProcessMaterial = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessMaterial); + static FName NAME_WidgetDrawSize = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, WidgetDrawSize); + static FName NAME_WindowFocusable = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bWindowFocusable); + static FName NAME_WindowVisibility = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, WindowVisibility); + static FName NAME_ReceiveHardwareInput = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bReceiveHardwareInput); + static FName NAME_RenderTargetBackgroundColor = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, RenderTargetBackgroundColor); + static FName NAME_RenderTargetBlendMode = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, RenderTargetBlendMode); + //static FName NAME_PostProcessTintColorAndOpacity = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessTintColorAndOpacity); + //static FName NAME_PostProcessOpacityFromTexture = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessOpacityFromTexture); + static FName NAME_DrawToVRPreview = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bDrawToVRPreview); + static FName NAME_VRDisplayType = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, VRDisplayType); + static FName NAME_PostVRDisplayType = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostVRDisplayType); + + if (Property->GetFName() == NAME_WidgetClass + || Property->GetFName() == NAME_EditorDisplayType + //|| Property->GetFName() == NAME_PostProcessMaterial + || Property->GetFName() == NAME_WidgetDrawSize + || Property->GetFName() == NAME_WindowFocusable + || Property->GetFName() == NAME_WindowVisibility + || Property->GetFName() == NAME_ReceiveHardwareInput + || Property->GetFName() == NAME_RenderTargetBackgroundColor + || Property->GetFName() == NAME_RenderTargetBlendMode + || Property->GetFName() == NAME_DrawToVRPreview + || Property->GetFName() == NAME_VRDisplayType + || Property->GetFName() == NAME_PostVRDisplayType) + //|| Property->GetFName() == NAME_PostProcessTintColorAndOpacity + //|| Property->GetFName() == NAME_PostProcessOpacityFromTexture) + { + bool bWasRequestedDisplay = bDisplayRequested; + UWorld* CurrentWorld = World.Get(); + Hide(); + if (bWasRequestedDisplay && CurrentWorld) + { + Display(CurrentWorld); + } + } + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif + +///////////////////////////////////////////////////// +// AVRFullScreenUserWidgetActor + +AVRFullScreenUserWidgetActor::AVRFullScreenUserWidgetActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +#if WITH_EDITOR + , bEditorDisplayRequested(false) +#endif //WITH_EDITOR +{ + ScreenUserWidget = CreateDefaultSubobject(TEXT("ScreenUserWidget")); + + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = false; + bAllowTickBeforeBeginPlay = false; + SetActorTickEnabled(false); + //SetHidden(false); + + bShowOnInit = false; +} + +void AVRFullScreenUserWidgetActor::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::PostLoad() +{ + Super::PostLoad(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::PostActorCreated() +{ + Super::PostActorCreated(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::Destroyed() +{ + if (ScreenUserWidget) + { + ScreenUserWidget->Hide(); + } + Super::Destroyed(); +} + +void AVRFullScreenUserWidgetActor::BeginPlay() +{ + if (ScreenUserWidget && bShowOnInit) + { + RequestGameDisplay(); + } + + Super::BeginPlay(); +} + +void AVRFullScreenUserWidgetActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (ScreenUserWidget) + { + UWorld* ActorWorld = GetWorld(); + if (ActorWorld && (ActorWorld->WorldType == EWorldType::Game || ActorWorld->WorldType == EWorldType::PIE)) + { + ScreenUserWidget->Hide(); + } + } +} + +void AVRFullScreenUserWidgetActor::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + +#if WITH_EDITOR + if (bEditorDisplayRequested) + { + bEditorDisplayRequested = false; + RequestEditorDisplay(); + } +#endif //WITH_EDITOR + + // Don't tick if not requested + if (ScreenUserWidget && ScreenUserWidget->IsDisplayRequested()) + { + ScreenUserWidget->Tick(DeltaSeconds); + } +} + +void AVRFullScreenUserWidgetActor::RequestEditorDisplay() +{ +#if WITH_EDITOR + UWorld* ActorWorld = GetWorld(); + if (ScreenUserWidget && ActorWorld && ActorWorld->WorldType == EWorldType::Editor) + { + ScreenUserWidget->Display(ActorWorld); + } +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::RequestGameDisplay() +{ + UWorld* ActorWorld = GetWorld(); + if (ScreenUserWidget && ActorWorld && (ActorWorld->WorldType == EWorldType::Game || ActorWorld->WorldType == EWorldType::PIE)) + { + ScreenUserWidget->Display(ActorWorld); + SetActorTickEnabled(true); + } +} + +void AVRFullScreenUserWidgetActor::SetWidgetVisible(bool bIsVisible) +{ + if (ScreenUserWidget) + { + if (!bIsVisible) + { + ScreenUserWidget->Hide(); + SetActorTickEnabled(false); + } + else + { + RequestGameDisplay(); + } + } +} + +UVRFullScreenUserWidget* AVRFullScreenUserWidgetActor::GetPreviewWidgetComp() +{ + return ScreenUserWidget; +} + +UUserWidget* AVRFullScreenUserWidgetActor::GetWidget() +{ + if (ScreenUserWidget) + { + return ScreenUserWidget->GetWidget(); + } + + return nullptr; +} + +UTextureRenderTarget2D* AVRFullScreenUserWidgetActor::GetPostProcessRenderTarget() +{ + if (ScreenUserWidget) + { + return ScreenUserWidget->GetPostProcessRenderTarget(); + } + + return nullptr; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp new file mode 100644 index 0000000..5c13d14 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp @@ -0,0 +1,201 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRLogComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRLogComponent) + +//#include "Engine/Engine.h" + +/* Top of File */ +#define LOCTEXT_NAMESPACE "VRLogComponent" + + //============================================================================= +UVRLogComponent::UVRLogComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; + MaxLineLength = 130; + MaxStoredMessages = 10000; +} + +//============================================================================= +UVRLogComponent::~UVRLogComponent() +{ + +} + + +void UVRLogComponent::SetConsoleText(FString Text) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + // Using append because UpdatePrecompletedInputLine is private and append calls it + ViewportConsole->SetInputText(""); + ViewportConsole->AppendInputText(Text); +} + +void UVRLogComponent::SendKeyEventToConsole(FKey Key, EInputEvent KeyEvent) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + ViewportConsole->FakeGotoState(FName(TEXT("Typing"))); + ViewportConsole->InputKey(IPlatformInputDeviceMapper::Get().GetDefaultInputDevice(), Key, KeyEvent); + ViewportConsole->FakeGotoState(NAME_None); +} + +void UVRLogComponent::AppendTextToConsole(FString Text, bool bReturnAtEnd) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + ViewportConsole->AppendInputText(Text); + + if (bReturnAtEnd) + { + ViewportConsole->FakeGotoState(FName(TEXT("Typing"))); + ViewportConsole->InputKey(IPlatformInputDeviceMapper::Get().GetDefaultInputDevice(), EKeys::Enter, EInputEvent::IE_Released); + ViewportConsole->FakeGotoState(NAME_None); + } + +} + +bool UVRLogComponent::DrawConsoleToRenderTarget2D(EBPVRConsoleDrawType DrawType, UTextureRenderTarget2D * Texture, float ScrollOffset, bool bForceDraw) +{ + if (!bForceDraw && DrawType == EBPVRConsoleDrawType::VRConsole_Draw_OutputLogOnly && !OutputLogHistory.bIsDirty) + { + return false; + } + //LastRenderedOutputLogSize + +// check(WorldContextObject); + UWorld* World = GetWorld();//GEngine->GetWorldFromContextObject(WorldContextObject, false); + + if (!World) + return false; + + // Create or find the canvas object to use to render onto the texture. Multiple canvas render target textures can share the same canvas. + UCanvas* Canvas = World->GetCanvasForRenderingToTarget(); + + if (!Canvas) + return false; + + // Create the FCanvas which does the actual rendering. + //const ERHIFeatureLevel::Type FeatureLevel = World != nullptr ? World->FeatureLevel : GMaxRHIFeatureLevel; + + FCanvas * RenderCanvas = new FCanvas( + Texture->GameThread_GetRenderTargetResource(), + nullptr, + World, + World->GetFeatureLevel(), + // Draw immediately so that interleaved SetVectorParameter (etc) function calls work as expected + FCanvas::CDM_ImmediateDrawing); + + Canvas->Init(Texture->GetSurfaceWidth(), Texture->GetSurfaceHeight(), nullptr, RenderCanvas); + Canvas->Update(); + + switch (DrawType) + { + //case EBPVRConsoleDrawType::VRConsole_Draw_ConsoleAndOutputLog: DrawConsole(true, Canvas); DrawOutputLog(true, Canvas); break; + case EBPVRConsoleDrawType::VRConsole_Draw_ConsoleOnly: DrawConsole(false, Canvas); break; + case EBPVRConsoleDrawType::VRConsole_Draw_OutputLogOnly: DrawOutputLog(false, Canvas, ScrollOffset); break; + default: break; + } + + // Clean up and flush the rendering canvas. + Canvas->Canvas = nullptr; + RenderCanvas->Flush_GameThread(); + delete RenderCanvas; + RenderCanvas = nullptr; + + // It renders without this, is it actually required? + // Enqueue the rendering command to copy the freshly rendering texture resource back to the render target RHI + // so that the texture is updated and available for rendering. + /*ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER + ( + CanvasRenderTargetResolveCommand, + FTextureRenderTargetResource*, + RenderTargetResource, + Texture->GameThread_GetRenderTargetResource(), + { + RHICmdList.CopyToResolveTarget(RenderTargetResource->GetRenderTargetTexture(), RenderTargetResource->TextureRHI, true, FResolveParams()); + } + );*/ + + return true; +} + + + +void UVRLogComponent::DrawConsole(bool bLowerHalf, UCanvas* Canvas) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + if (!ViewportConsole) + return; + + ViewportConsole->PostRender_Console_Open(Canvas); + +} + +void UVRLogComponent::DrawOutputLog(bool bUpperHalf, UCanvas* Canvas, float ScrollOffset) +{ + UFont* Font = GEngine->GetSmallFont();// GEngine->GetTinyFont();//GEngine->GetSmallFont(); + + // determine the height of the text + float xl, yl; + Canvas->StrLen(Font, TEXT("M"), xl, yl); + float Height = FMath::FloorToFloat(Canvas->ClipY);// *0.75f); + + + // Background + FLinearColor BackgroundColor = FColor::Black.ReinterpretAsLinear(); + BackgroundColor.A = 1.0f; + FCanvasTileItem ConsoleTile(FVector2D(0, 0.0f), GBlackTexture, FVector2D(Canvas->ClipX, Canvas->ClipY), FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f), BackgroundColor); + + // Preserve alpha to allow single-pass composite + ConsoleTile.BlendMode = SE_BLEND_AlphaBlend; + + Canvas->DrawItem(ConsoleTile); + + FCanvasTextItem ConsoleText(FVector2D(0, 0 + Height - 5 - yl), FText::FromString(TEXT("")), Font, FColor::Emerald); + + const TArray< TSharedPtr > LoggedMessages = OutputLogHistory.GetMessages(); + + int32 ScrollPos = 0; + + if(ScrollOffset > 0 && LoggedMessages.Num() > 1) + ScrollPos = FMath::Clamp(FMath::RoundToInt(LoggedMessages.Num() * ScrollOffset ) , 0, LoggedMessages.Num() - 1); + + float Xpos = 0.0f; + float Ypos = 0.0f; + for (int i = LoggedMessages.Num() - (1 + ScrollPos); i >= 0 && Ypos <= Height - yl; i--)//auto &Message : LoggedMessages) + { + switch (LoggedMessages[i]->Verbosity) + { + + case ELogVerbosity::Error: + case ELogVerbosity::Fatal: ConsoleText.SetColor(FLinearColor(0.7f,0.1f,0.1f)); break; + case ELogVerbosity::Warning: ConsoleText.SetColor(FLinearColor(0.5f,0.5f,0.0f)); break; + + case ELogVerbosity::Log: + default: ConsoleText.SetColor(FLinearColor(0.8f,0.8f,0.8f)); + } + + Ypos += yl; + ConsoleText.Text = FText::Format(NSLOCTEXT("VRLogComponent", "ConsoleFormat", "{0}"), FText::FromString(*LoggedMessages[i]->Message)); + Canvas->DrawItem(ConsoleText, 0, Height - Ypos); + } + + OutputLogHistory.bIsDirty = false; +} + + + +#undef LOCTEXT_NAMESPACE +/* Bottom of File */ \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp new file mode 100644 index 0000000..a7c4ba8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp @@ -0,0 +1,124 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRPlayerStart.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRPlayerStart) + +#include "Components/CapsuleComponent.h" +#include "Components/BillboardComponent.h" + + +AVRPlayerStart::AVRPlayerStart(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRRootComp = CreateDefaultSubobject(TEXT("VRRootComp")); + VRRootComp->Mobility = EComponentMobility::Static; + RootComponent = VRRootComp; + + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (CapsuleComp && VRRootComp) + { + CapsuleComp->SetupAttachment(VRRootComp); + CapsuleComp->SetRelativeLocation(FVector(0.f,0.f,CapsuleComp->GetScaledCapsuleHalfHeight())); + } +} + +void AVRPlayerStart::GetSimpleCollisionCylinder(float& CollisionRadius, float& CollisionHalfHeight) const +{ + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (CapsuleComp != nullptr && CapsuleComp->IsRegistered() && CapsuleComp->IsCollisionEnabled()) + { + // Note: assuming vertical orientation + CapsuleComp->GetScaledCapsuleSize(CollisionRadius, CollisionHalfHeight); + } + else + { + Super::GetSimpleCollisionCylinder(CollisionRadius, CollisionHalfHeight); + } +} +void AVRPlayerStart::FindBase() +{ + if (GetWorld()->HasBegunPlay()) + { + return; + } + if (ShouldBeBased()) + { + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (!CapsuleComp) + return; + // not using find base, because don't want to fail if LD has navigationpoint slightly interpenetrating floor + FHitResult Hit(1.f); + const float Radius = CapsuleComp->GetScaledCapsuleRadius(); + FVector const CollisionSlice(Radius, Radius, 1.f); + // check for placement + float ScaledHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight(); + const FVector TraceStart = GetActorLocation() + FVector(0.f, 0.f, ScaledHalfHeight); + const FVector TraceEnd = GetActorLocation() - FVector(0.f, 0.f, 2.f * ScaledHalfHeight); + GetWorld()->SweepSingleByObjectType(Hit, TraceStart, TraceEnd, FQuat::Identity, FCollisionObjectQueryParams(ECC_WorldStatic), FCollisionShape::MakeBox(CollisionSlice), FCollisionQueryParams(SCENE_QUERY_STAT(NavFindBase), false)); + // @fixme, ensure object is on the navmesh? + // if( Hit.Actor != NULL ) + // { + // if (Hit.Normal.Z > Scout->WalkableFloorZ) + // { + // const FVector HitLocation = TraceStart + (TraceEnd - TraceStart) * Hit.Time; + // TeleportTo(HitLocation + FVector(0.f,0.f,CapsuleComponent->GetScaledCapsuleHalfHeight()-2.f), GetActorRotation(), false, true); + // } + // else + // { + // Hit.Actor = NULL; + // } + // } + if (GetGoodSprite()) + { + GetGoodSprite()->SetVisibility(true); + } + if (GetBadSprite()) + { + GetBadSprite()->SetVisibility(false); + } + } +} +void AVRPlayerStart::Validate() +{ + if (ShouldBeBased() && (GetGoodSprite() || GetBadSprite())) + { + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (!CapsuleComp) + return; + FVector OrigLocation = GetActorLocation(); + const float Radius = CapsuleComp->GetScaledCapsuleRadius(); + FVector const Slice(Radius, Radius, 1.f); + bool bResult = true; + // Check for adjustment + FHitResult Hit(ForceInit); + float ScaledHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight(); + const FVector TraceStart = GetActorLocation() + FVector(0.f, 0.f, ScaledHalfHeight); + const FVector TraceEnd = GetActorLocation() - FVector(0.f, 0.f, 4.f * ScaledHalfHeight); + GetWorld()->SweepSingleByChannel(Hit, TraceStart, TraceEnd, FQuat::Identity, ECC_Pawn, FCollisionShape::MakeBox(Slice), FCollisionQueryParams(SCENE_QUERY_STAT(NavObjectBase_Validate), false, this)); + if (Hit.bBlockingHit) + { + const FVector HitLocation = TraceStart + (TraceEnd - TraceStart) * Hit.Time; + FVector Dest = HitLocation - FVector(0.f, 0.f, /*CapsuleComponent->GetScaledCapsuleHalfHeight() -*/ 2.f); + // Move actor (TEST ONLY) to see if navigation point moves + TeleportTo(Dest, GetActorRotation(), false, true); + // If only adjustment was down towards the floor, then it is a valid placement + FVector NewLocation = GetActorLocation(); + bResult = (NewLocation.X == OrigLocation.X && + NewLocation.Y == OrigLocation.Y && + NewLocation.Z <= OrigLocation.Z); + // Move actor back to original position + TeleportTo(OrigLocation, GetActorRotation(), false, true); + } + // Update sprites by result + if (GetGoodSprite()) + { + GetGoodSprite()->SetVisibility(bResult); + } + if (GetBadSprite()) + { + GetBadSprite()->SetVisibility(!bResult); + } + } + // Force update of icon + MarkComponentsRenderStateDirty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp new file mode 100644 index 0000000..8b0b74d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp @@ -0,0 +1,1794 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRRenderTargetManager.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRRenderTargetManager) + +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerState.h" +#include "GameFramework/PlayerController.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet/KismetRenderingLibrary.h" +#include "Engine/CanvasRenderTarget2D.h" +#include "Engine/Canvas.h" +#include "GeomTools.h" +#include "Serialization/ArchiveSaveCompressedProxy.h" +#include "Serialization/ArchiveLoadCompressedProxy.h" +#include "Materials/Material.h" +#include "Net/UnrealNetwork.h" + +namespace RLE_Funcs +{ + enum RLE_Flags + { + RLE_CompressedByte = 1, + RLE_CompressedShort = 2, + RLE_Compressed24 = 3, + RLE_NotCompressedByte = 4, + RLE_NotCompressedShort = 5, + RLE_NotCompressed24 = 6, + //RLE_Empty = 3, + //RLE_AllSame = 4, + RLE_ContinueRunByte = 7, + RLE_ContinueRunShort = 8, + RLE_ContinueRun24 = 9 + }; + + template + static bool RLEEncodeLine(TArray* LineToEncode, TArray* EncodedLine); + + template + static bool RLEEncodeBuffer(DataType* BufferToEncode, uint32 EncodeLength, TArray* EncodedLine); + + template + static void RLEDecodeLine(TArray* LineToDecode, TArray* DecodedLine, bool bCompressed); + + template + static void RLEDecodeLine(const uint8* LineToDecode, uint32 Num, TArray* DecodedLine, bool bCompressed); + + static inline void RLEWriteContinueFlag(uint32 Count, uint8** loc); + + template + static inline void RLEWriteRunFlag(uint32 Count, uint8** loc, TArray& Data, bool bCompressed); +} + +UVRRenderTargetManager::UVRRenderTargetManager(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + PollRelevancyTime = 0.1f; + DrawRate = 0.0333; + + bIsStoringImage = false; + RenderTarget = nullptr; + RenderTargetWidth = 100; + RenderTargetHeight = 100; + ClearColor = FColor::White; + + TextureBlobSize = 512; + MaxBytesPerSecondRate = 5000; + + bInitiallyReplicateTexture = false; + bIsLoadingTextureBuffer = false; + + OwnerIDCounter = 0; +} + +bool UVRRenderTargetManager::SendDrawOperations_Validate(const TArray& RenderOperationStoreList) +{ + return true; +} + +void UVRRenderTargetManager::SendDrawOperations_Implementation(const TArray& RenderOperationStoreList) +{ + if (GetNetMode() == ENetMode::NM_Client) + { + RenderOperationStore.Append(RenderOperationStoreList); + } + + DrawOperations(); +} + + +void UVRRenderTargetManager::AddLineDrawOperation(FVector2D Point1, FVector2D Point2, FColor Color, int32 Thickness) +{ + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_LineDraw; + NewOperation.Color = Color; + NewOperation.P1 = Point1; + NewOperation.P2 = Point2; + NewOperation.Thickness = (uint32)Thickness; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::AddTextureDrawOperation(FVector2D Position, UTexture2D* TextureToDisplay) +{ + + if (!TextureToDisplay) + return; + + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_TexDraw; + NewOperation.P1 = Position; + NewOperation.Texture = TextureToDisplay; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::AddMaterialTrianglesDrawOperation(TArray Tris, UMaterial* Material) +{ + + if (!Tris.Num()) + return; + + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_TriDraw; + NewOperation.Color = Tris[0].V0_Color.ToFColor(true); + + NewOperation.Tris.AddUninitialized(Tris.Num()); + int Counter = 0; + FRenderManagerTri RenderTri; + for (FCanvasUVTri Tri : Tris) + { + RenderTri.P1 = Tri.V0_Pos; + RenderTri.P2 = Tri.V1_Pos; + RenderTri.P3 = Tri.V2_Pos; + NewOperation.Tris[Counter++] = RenderTri; + } + + NewOperation.Material = Material; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::DrawOperation(UCanvas* Canvas, const FRenderManagerOperation& Operation) +{ + if (IsValid(LocalProxy) && LocalProxy->OwnersID == Operation.OwnerID) + { + return; + } + + switch (Operation.OperationType) + { + case ERenderManagerOperationType::Op_LineDraw: + { + FCanvasLineItem LineItem; + LineItem.Origin = FVector(Operation.P1.X, Operation.P1.Y, 0.f); + LineItem.EndPos = FVector(Operation.P2.X, Operation.P2.Y, 0.f); + LineItem.LineThickness = (float)Operation.Thickness; + LineItem.SetColor(Operation.Color.ReinterpretAsLinear()); + Canvas->DrawItem(LineItem); + }break; + case ERenderManagerOperationType::Op_TexDraw: + { + if (Operation.Texture && Operation.Texture->GetResource()) + { + //FTexture* RenderTextureResource = (RenderBase) ? RenderBase->Resource : GWhiteTexture; + FCanvasTileItem TileItem(Operation.P1, Operation.Texture->GetResource(), FVector2D(Operation.Texture->GetSizeX(), Operation.Texture->GetSizeY()), FVector2D(0, 0), FVector2D(1.f, 1.f), ClearColor); + TileItem.BlendMode = FCanvas::BlendToSimpleElementBlend(EBlendMode::BLEND_Translucent); + Canvas->DrawItem(TileItem); + } + }break; + case ERenderManagerOperationType::Op_TriDraw: + { + if (Operation.Tris.Num() && Operation.Material) + { + FCanvasTriangleItem TriangleItem(FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector, NULL); + TriangleItem.MaterialRenderProxy = Operation.Material->GetRenderProxy(); + + FCanvasUVTri triStore; + triStore.V0_Color = Operation.Color; + triStore.V1_Color = Operation.Color; + triStore.V2_Color = Operation.Color; + + TriangleItem.TriangleList.Reset(Operation.Tris.Num()); + TriangleItem.TriangleList.AddUninitialized(Operation.Tris.Num()); + uint32 Counter = 0; + for (FRenderManagerTri Tri : Operation.Tris) + { + triStore.V0_Pos = Tri.P1; + triStore.V1_Pos = Tri.P2; + triStore.V2_Pos = Tri.P3; + TriangleItem.TriangleList[Counter++] = triStore; + } + + Canvas->DrawItem(TriangleItem); + } + }break; + } + +} + +void UVRRenderTargetManager::DrawPoll() +{ + if (!RenderOperationStore.Num() && !LocalRenderOperationStore.Num()) + { + GetWorld()->GetTimerManager().ClearTimer(DrawHandle); + return; + } + + if (GetNetMode() < ENetMode::NM_Client) + { + SendDrawOperations(RenderOperationStore); + } + else + { + if (LocalRenderOperationStore.Num()) + { + // Send operations to server + if (IsValid(LocalProxy)) + { + LocalProxy->SendLocalDrawOperations(LocalRenderOperationStore); + } + + RenderOperationStore.Append(LocalRenderOperationStore); + LocalRenderOperationStore.Empty(); + } + + DrawOperations(); + } +} + +void UVRRenderTargetManager::DrawOperations() +{ + + if (bIsLoadingTextureBuffer) + { + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + return; + } + + if (GetNetMode() == ENetMode::NM_DedicatedServer) + { + RenderOperationStore.Empty(); + return; + } + + UWorld* World = GetWorld(); + + if (!World || !World->bBegunPlay) + return; + + // Reference to the Render Target resource + FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + if (!RenderTargetResource) + { + RenderOperationStore.Empty(); + return; + } + + // Retrieve a UCanvas form the world to avoid creating a new one each time + UCanvas* CanvasToUse = World->GetCanvasForDrawMaterialToRenderTarget(); + + // Creates a new FCanvas for rendering + FCanvas RenderCanvas( + RenderTargetResource, + nullptr, + World, + World->GetFeatureLevel()); + + // Setup the canvas with the FCanvas reference + CanvasToUse->Init(RenderTarget->SizeX, RenderTarget->SizeY, nullptr, &RenderCanvas); + CanvasToUse->Update(); + + if (CanvasToUse) + { + for (const FRenderManagerOperation& opt : RenderOperationStore) + { + DrawOperation(CanvasToUse, opt); + } + + RenderOperationStore.Empty(); + + // Perform the drawing + RenderCanvas.Flush_GameThread(); + + // Cleanup the FCanvas reference, to delete it + CanvasToUse->Canvas = NULL; + } +} + + +ARenderTargetReplicationProxy::ARenderTargetReplicationProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bOnlyRelevantToOwner = true; + bNetUseOwnerRelevancy = true; + bReplicates = true; + PrimaryActorTick.bCanEverTick = false; + SetReplicateMovement(false); + bWaitingForManager = false; +} + +void ARenderTargetReplicationProxy::OnRep_Manager() +{ + // If our manager is valid, save off a reference to ourselves to the local copy. + if (IsValid(OwningManager)) + { + OwningManager->LocalProxy = this; + + // If we loaded a texture before the manager loaded + if (bWaitingForManager) + { + OwningManager->bIsLoadingTextureBuffer = false; + OwningManager->RenderTargetStore = TextureStore; + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, total byte count: %i"), OwningManager->RenderTargetStore.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + bWaitingForManager = false; + } + } +} + +bool ARenderTargetReplicationProxy::SendLocalDrawOperations_Validate(const TArray& LocalRenderOperationStoreList) +{ + return true; +} + +void ARenderTargetReplicationProxy::SendLocalDrawOperations_Implementation(const TArray& LocalRenderOperationStoreList) +{ + if (IsValid(OwningManager)) + { + OwningManager->RenderOperationStore.Append(LocalRenderOperationStoreList); + + // ID the render operations to the player that sent them in + if (APlayerController* OwningPlayer = Cast(GetOwner())) + { + for (int i = (OwningManager->RenderOperationStore.Num() - LocalRenderOperationStoreList.Num()); i < OwningManager->RenderOperationStore.Num(); i++) + { + OwningManager->RenderOperationStore[i].OwnerID = OwnersID; + } + } + + if (!OwningManager->DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(OwningManager->DrawHandle, OwningManager.Get(), &UVRRenderTargetManager::DrawPoll, OwningManager->DrawRate, true); + } +} + +void ARenderTargetReplicationProxy::ReceiveTexture_Implementation(const FBPVRReplicatedTextureStore& TextureData) +{ + if (IsValid(OwningManager)) + { + OwningManager->RenderTargetStore = TextureData; + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, byte count: %i"), TextureData.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + } +} + +void ARenderTargetReplicationProxy::InitTextureSend_Implementation(int32 Width, int32 Height, int32 TotalDataCount, int32 BlobCount, EPixelFormat PixelFormat, bool bIsZipped/*, bool bIsJPG*/) +{ + TextureStore.Reset(); + TextureStore.PixelFormat = PixelFormat; + TextureStore.bIsZipped = bIsZipped; + //TextureStore.bJPG = bIsJPG; + TextureStore.Width = Width; + TextureStore.Height = Height; + + TextureStore.PackedData.Reset(TotalDataCount); + TextureStore.PackedData.AddUninitialized(TotalDataCount); + + BlobNum = BlobCount; + + if (IsValid(OwningManager)) + { + OwningManager->bIsLoadingTextureBuffer = true; + } + + Ack_InitTextureSend(TotalDataCount); +} + +bool ARenderTargetReplicationProxy::Ack_InitTextureSend_Validate(int32 TotalDataCount) +{ + return true; +} + +void ARenderTargetReplicationProxy::Ack_InitTextureSend_Implementation(int32 TotalDataCount) +{ + if (TotalDataCount == TextureStore.PackedData.Num()) + { + BlobNum = 0; + + // Calculate time offset to achieve our max bytes per second with the given blob size + float SendRate = 1.f / (MaxBytesPerSecondRate / (float)TextureBlobSize); + + GetWorld()->GetTimerManager().SetTimer(SendTimer_Handle, this, &ARenderTargetReplicationProxy::SendNextDataBlob, SendRate, true); + + // Start sending data blobs + //SendNextDataBlob(); + } +} + +void ARenderTargetReplicationProxy::SendInitMessage() +{ + int32 TotalBlobs = TextureStore.PackedData.Num() / TextureBlobSize + (TextureStore.PackedData.Num() % TextureBlobSize > 0 ? 1 : 0); + + InitTextureSend(TextureStore.Width, TextureStore.Height, TextureStore.PackedData.Num(), TotalBlobs, TextureStore.PixelFormat, TextureStore.bIsZipped/*, TextureStore.bJPG*/); + +} + +void ARenderTargetReplicationProxy::SendNextDataBlob() +{ + if (!IsValid(this) || !this->GetOwner() || !IsValid(this->GetOwner())) + { + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + BlobNum = 0; + if (SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + + return; + } + + BlobNum++; + int32 TotalBlobs = TextureStore.PackedData.Num() / TextureBlobSize + (TextureStore.PackedData.Num() % TextureBlobSize > 0 ? 1 : 0); + + if (BlobNum <= TotalBlobs) + { + TArray BlobStore; + int32 BlobLen = (BlobNum == TotalBlobs ? TextureStore.PackedData.Num() % TextureBlobSize : TextureBlobSize); + + + BlobStore.AddUninitialized(BlobLen); + uint8* MemLoc = TextureStore.PackedData.GetData(); + int32 MemCount = (BlobNum - 1) * TextureBlobSize; + MemLoc += MemCount; + + FMemory::Memcpy(BlobStore.GetData(), MemLoc, BlobLen); + + ReceiveTextureBlob(BlobStore, MemCount, BlobNum); + } + else + { + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + if (SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + BlobNum = 0; + } +} + +//============================================================================= +void ARenderTargetReplicationProxy::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + + DOREPLIFETIME(ARenderTargetReplicationProxy, OwningManager); + DOREPLIFETIME(ARenderTargetReplicationProxy, OwnersID); +} + +void ARenderTargetReplicationProxy::ReceiveTextureBlob_Implementation(const TArray& TextureBlob, int32 LocationInData, int32 BlobNumber) +{ + if (LocationInData + TextureBlob.Num() <= TextureStore.PackedData.Num()) + { + uint8* MemLoc = TextureStore.PackedData.GetData(); + MemLoc += LocationInData; + FMemory::Memcpy(MemLoc, TextureBlob.GetData(), TextureBlob.Num()); + + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture blob, byte count: %i"), TextureBlob.Num())); + } + + if (BlobNumber == BlobNum) + { + Ack_ReceiveTextureBlob(BlobNum); + + // We finished, unpack and display + if (IsValid(OwningManager)) + { + OwningManager->bIsLoadingTextureBuffer = false; + OwningManager->RenderTargetStore = TextureStore; + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, total byte count: %i"), OwningManager->RenderTargetStore.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + } + else + { + bWaitingForManager = true; + } + } + +} + +bool ARenderTargetReplicationProxy::Ack_ReceiveTextureBlob_Validate(int32 BlobCount) +{ + return true; +} + +void ARenderTargetReplicationProxy::Ack_ReceiveTextureBlob_Implementation(int32 BlobCount) +{ + // Send next data blob + //SendNextDataBlob(); + +} + +void UVRRenderTargetManager::UpdateRelevancyMap() +{ + AActor* myOwner = GetOwner(); + + for (int i = NetRelevancyLog.Num() - 1; i >= 0; i--) + { + if (!IsValid(NetRelevancyLog[i].PC) || NetRelevancyLog[i].PC->IsLocalController() || !NetRelevancyLog[i].PC->GetPawn()) + { + NetRelevancyLog[i].ReplicationProxy->Destroy(); + NetRelevancyLog.RemoveAt(i); + } + else + { + if (APawn* pawn = NetRelevancyLog[i].PC->GetPawn()) + { + if (!myOwner->IsNetRelevantFor(NetRelevancyLog[i].PC.Get(), pawn, pawn->GetActorLocation())) + { + NetRelevancyLog[i].bIsRelevant = false; + NetRelevancyLog[i].bIsDirty = false; + //NetRelevancyLog.RemoveAt(i); + } + } + } + } + + + bool bHadDirtyActors = false; + + for (FConstPlayerControllerIterator PCIt = GetWorld()->GetPlayerControllerIterator(); PCIt; ++PCIt) + { + if (APlayerController* PC = PCIt->Get()) + { + if (PC->IsLocalController()) + continue; + + if (!PC->HasClientLoadedCurrentWorld()) + continue; + + if (APawn* pawn = PC->GetPawn()) + { + + if (myOwner->IsNetRelevantFor(PC, pawn, pawn->GetActorLocation())) + { + FClientRepData* RepData = NetRelevancyLog.FindByPredicate([PC](const FClientRepData& Other) + { + return Other.PC == PC; + }); + + if (!RepData) + { + FClientRepData ClientRepData; + + FTransform NewTransform = this->GetOwner()->GetActorTransform(); + ARenderTargetReplicationProxy* RenderProxy = GetWorld()->SpawnActorDeferred(ARenderTargetReplicationProxy::StaticClass(), NewTransform, PC); + if (RenderProxy) + { + RenderProxy->OwnersID = ++OwnerIDCounter; + RenderProxy->OwningManager = this; + RenderProxy->MaxBytesPerSecondRate = MaxBytesPerSecondRate; + RenderProxy->TextureBlobSize = TextureBlobSize; + UGameplayStatics::FinishSpawningActor(RenderProxy, NewTransform); + } + + if (RenderProxy) + { + RenderProxy->AttachToActor(this->GetOwner(), FAttachmentTransformRules::SnapToTargetIncludingScale); + + + ClientRepData.PC = PC; + ClientRepData.ReplicationProxy = RenderProxy; + ClientRepData.bIsRelevant = true; + ClientRepData.bIsDirty = true; + bHadDirtyActors = true; + NetRelevancyLog.Add(ClientRepData); + } + // Update this client with the new data + } + else + { + if (!RepData->bIsRelevant) + { + RepData->bIsRelevant = true; + RepData->bIsDirty = true; + bHadDirtyActors = true; + } + } + } + } + + } + } + + if (bHadDirtyActors && bInitiallyReplicateTexture && GetNetMode() != ENetMode::NM_DedicatedServer) + { + QueueImageStore(); + } +} + +bool UVRRenderTargetManager::DeCompressRenderTarget2D() +{ + if (!RenderTarget) + return false; + + RenderTargetStore.UnPackData(); + + + int32 Width = RenderTargetStore.Width; + int32 Height = RenderTargetStore.Height; + EPixelFormat PixelFormat = RenderTargetStore.PixelFormat; + uint8 PixelFormat8 = 0; + + TArray FinalColorData; + FinalColorData.AddUninitialized(RenderTargetStore.UnpackedData.Num()); + + uint32 Counter = 0; + FColor ColorVal; + ColorVal.A = 0xFF; + for (uint16 CompColor : RenderTargetStore.UnpackedData) + { + //CompColor.FillTo(ColorVal); + ColorVal.R = CompColor << 3; + ColorVal.G = CompColor >> 5 << 2; + ColorVal.B = CompColor >> 11 << 3; + ColorVal.A = 0xFF; + FinalColorData[Counter++] = ColorVal; + } + + // Write this to a texture2d + UTexture2D* RenderBase = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8);// RenderTargetStore.PixelFormat); + + // Switched to a Memcpy instead of byte by byte transer + uint8* MipData = (uint8*)RenderBase->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(MipData, (void*)FinalColorData.GetData(), FinalColorData.Num() * sizeof(FColor)); + RenderBase->GetPlatformData()->Mips[0].BulkData.Unlock(); + + //Setting some Parameters for the Texture and finally returning it + RenderBase->GetPlatformData()->SetNumSlices(1); + RenderBase->NeverStream = true; + RenderBase->SRGB = true; + //Avatar->CompressionSettings = TC_EditorIcon; + + RenderBase->UpdateResource(); + + /*uint32 size = sizeof(FColor); + + uint8* pData = new uint8[FinalColorData.Num() * sizeof(FColor)]; + FMemory::Memcpy(pData, (void*)FinalColorData.GetData(), FinalColorData.Num() * sizeof(FColor)); + + UTexture2D* TexturePtr = RenderBase; + const uint8* TextureData = pData; + ENQUEUE_RENDER_COMMAND(VRRenderTargetManager_FillTexture)( + [TexturePtr, TextureData](FRHICommandList& RHICmdList) + { + FUpdateTextureRegion2D region; + region.SrcX = 0; + region.SrcY = 0; + region.DestX = 0; + region.DestY = 0; + region.Width = TexturePtr->GetSizeX();// TEX_WIDTH; + region.Height = TexturePtr->GetSizeY();//TEX_HEIGHT; + + FTexture2DResource* resource = (FTexture2DResource*)TexturePtr->Resource; + RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * GPixelFormats[TexturePtr->GetPixelFormat()].BlockBytes, TextureData); + delete[] TextureData; + });*/ + + + // Using this as it saves custom implementation + + UWorld* World = GetWorld(); + + // Reference to the Render Target resource + FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + // Retrieve a UCanvas form the world to avoid creating a new one each time + UCanvas* CanvasToUse = World->GetCanvasForDrawMaterialToRenderTarget(); + + // Creates a new FCanvas for rendering + FCanvas RenderCanvas( + RenderTargetResource, + nullptr, + World, + World->GetFeatureLevel()); + + // Setup the canvas with the FCanvas reference + CanvasToUse->Init(RenderTarget->SizeX, RenderTarget->SizeY, nullptr, &RenderCanvas); + CanvasToUse->Update(); + + if (CanvasToUse) + { + FTexture* RenderTextureResource = (RenderBase) ? RenderBase->GetResource() : GWhiteTexture; + FCanvasTileItem TileItem(FVector2D(0, 0), RenderTextureResource, FVector2D(RenderTarget->SizeX, RenderTarget->SizeY), FVector2D(0, 0), FVector2D(1.f, 1.f), FLinearColor::White); + TileItem.BlendMode = FCanvas::BlendToSimpleElementBlend(EBlendMode::BLEND_Opaque); + CanvasToUse->DrawItem(TileItem); + + + // Perform the drawing + RenderCanvas.Flush_GameThread(); + + // Cleanup the FCanvas reference, to delete it + CanvasToUse->Canvas = NULL; + } + + RenderBase->ReleaseResource(); + RenderBase->MarkAsGarbage(); + + return true; +} + +void UVRRenderTargetManager::QueueImageStore() +{ + + if (!bInitiallyReplicateTexture || !RenderTarget || bIsStoringImage || GetNetMode() == ENetMode::NM_DedicatedServer) + { + return; + } + + bIsStoringImage = true; + + // Init new RenderRequest + FRenderDataStore* renderData = new FRenderDataStore(); + + // Get RenderContext + FTextureRenderTargetResource* renderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + if (!renderTargetResource) + return; + + renderData->Size2D = renderTargetResource->GetSizeXY(); + renderData->PixelFormat = RenderTarget->GetFormat(); + + struct FReadSurfaceContext { + FRenderTarget* SrcRenderTarget; + TArray* OutData; + FIntRect Rect; + FReadSurfaceDataFlags Flags; + }; + + // Setup GPU command + FReadSurfaceContext readSurfaceContext = + { + renderTargetResource, + &(renderData->ColorData), + FIntRect(0,0,renderTargetResource->GetSizeXY().X, renderTargetResource->GetSizeXY().Y), + FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX) + }; + + ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)( + [readSurfaceContext](FRHICommandListImmediate& RHICmdList) { + RHICmdList.ReadSurfaceData( + readSurfaceContext.SrcRenderTarget->GetRenderTargetTexture(), + readSurfaceContext.Rect, + *readSurfaceContext.OutData, + readSurfaceContext.Flags + ); + }); + + // Notify new task in RenderQueue + RenderDataQueue.Enqueue(renderData); + + // Set RenderCommandFence + renderData->RenderFence.BeginFence(); + + this->SetComponentTickEnabled(true); +} + +void UVRRenderTargetManager::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Read pixels once RenderFence is completed + if (!bInitiallyReplicateTexture || RenderDataQueue.IsEmpty() || GetNetMode() == ENetMode::NM_DedicatedServer) + { + SetComponentTickEnabled(false); + } + else + { + // Peek the next RenderRequest from queue + FRenderDataStore* nextRenderData; + RenderDataQueue.Peek(nextRenderData); + + if (nextRenderData) + { + if (nextRenderData->RenderFence.IsFenceComplete()) + { + bIsStoringImage = false; + RenderTargetStore.Reset(); + uint32 SizeOfData = nextRenderData->ColorData.Num(); + + RenderTargetStore.UnpackedData.Reset(SizeOfData); + RenderTargetStore.UnpackedData.AddUninitialized(SizeOfData); + + uint16 ColorVal = 0; + uint32 Counter = 0; + + // Convert to 16bit color + for (FColor col : nextRenderData->ColorData) + { + ColorVal = (col.R >> 3) << 11 | (col.G >> 2) << 5 | (col.B >> 3); + RenderTargetStore.UnpackedData[Counter++] = ColorVal; + } + + FIntPoint Size2D = nextRenderData->Size2D; + RenderTargetStore.Width = Size2D.X; + RenderTargetStore.Height = Size2D.Y; + RenderTargetStore.PixelFormat = nextRenderData->PixelFormat; + RenderTargetStore.PackData(); + + +//#if WITH_PUSH_MODEL + //MARK_PROPERTY_DIRTY_FROM_NAME(UVRRenderTargetManager, RenderTargetStore, this); +//#endif + + + // Delete the first element from RenderQueue + RenderDataQueue.Pop(); + delete nextRenderData; + + for (int i = NetRelevancyLog.Num() - 1; i >= 0; i--) + { + if (NetRelevancyLog[i].bIsDirty && IsValid(NetRelevancyLog[i].PC) && !NetRelevancyLog[i].PC->IsLocalController()) + { + if (IsValid(NetRelevancyLog[i].ReplicationProxy)) + { + NetRelevancyLog[i].ReplicationProxy->TextureStore = RenderTargetStore; + NetRelevancyLog[i].ReplicationProxy->SendInitMessage(); + NetRelevancyLog[i].bIsDirty = false; + } + } + } + + + } + } + } + +} + +void UVRRenderTargetManager::BeginPlay() +{ + Super::BeginPlay(); + + InitRenderTarget(); + + if (/*bInitiallyReplicateTexture && */GetNetMode() < ENetMode::NM_Client) + GetWorld()->GetTimerManager().SetTimer(NetRelevancyTimer_Handle, this, &UVRRenderTargetManager::UpdateRelevancyMap, PollRelevancyTime, true); +} + +void UVRRenderTargetManager::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + FRenderDataStore* Store = nullptr; + while (!RenderDataQueue.IsEmpty()) + { + RenderDataQueue.Dequeue(Store); + + if (Store) + { + delete Store; + } + } + + if (GetNetMode() < ENetMode::NM_Client) + GetWorld()->GetTimerManager().ClearTimer(NetRelevancyTimer_Handle); + + if(DrawHandle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(DrawHandle); + + if (RenderTarget) + { + RenderTarget->ReleaseResource(); + RenderTarget = nullptr; + } + + for (FClientRepData& RepData : NetRelevancyLog) + { + RepData.PC = nullptr; + if (IsValid(RepData.ReplicationProxy.Get())) + { + RepData.ReplicationProxy->Destroy(); + } + + RepData.ReplicationProxy = nullptr; + } + +} + +void UVRRenderTargetManager::InitRenderTarget() +{ + if (this->GetNetMode() == ENetMode::NM_DedicatedServer) + { + return; // Dedicated servers cannot handle render targets + } + + UWorld* World = GetWorld(); + + if (RenderTargetWidth > 0 && RenderTargetHeight > 0 && World) + { + RenderTarget = NewObject(this); + if (RenderTarget) + { + //NewCanvasRenderTarget->World = World; + RenderTarget->InitAutoFormat(RenderTargetWidth, RenderTargetHeight); + RenderTarget->ClearColor = ClearColor; + RenderTarget->bAutoGenerateMips = false; + RenderTarget->UpdateResourceImmediate(true); + } + else + { + RenderTarget = nullptr; + } + } + else + { + RenderTarget = nullptr; + } +} + + +bool UVRRenderTargetManager::GenerateTrisFromBoxPlaneIntersection(UPrimitiveComponent* PrimToBoxCheck, FTransform WorldTransformOfPlane, const FPlane& LocalProjectionPlane, FVector2D PlaneSize, FColor UVColor, TArray& OutTris) +{ + + if (!PrimToBoxCheck) + return false; + + OutTris.Reset(); + + FBoxSphereBounds LocalBounds = PrimToBoxCheck->CalcLocalBounds(); + FVector Center = LocalBounds.Origin; + FVector Extent = LocalBounds.BoxExtent; + + // Transform into plane local space from our localspace + FTransform LocalTrans = PrimToBoxCheck->GetComponentTransform() * WorldTransformOfPlane.Inverse(); + + FVector BoxMin = Center - Extent; + FVector BoxMax = Center + Extent; + + TArray PointList; + PointList.AddUninitialized(8); // 8 Is number of points on box + + PointList[0] = LocalTrans.TransformPosition(BoxMin); + PointList[1] = LocalTrans.TransformPosition(BoxMax); + PointList[2] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMin.Y, BoxMax.Z)); + PointList[3] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMax.Y, BoxMin.Z)); + PointList[4] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMin.Y, BoxMin.Z)); + PointList[5] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMax.Y, BoxMax.Z)); + PointList[6] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMin.Y, BoxMax.Z)); + PointList[7] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMax.Y, BoxMin.Z)); + + // List of edges to check, 12 total, 2 points per edge + int EdgeList[24] = + { + 0, 3, + 0, 4, + 0, 2, + 2, 5, + 2, 6, + 4, 7, + 4, 6, + 6, 1, + 1, 7, + 1, 5, + 5, 3, + 3, 7 + }; + + TArray IntersectionPoints; + + FVector Intersection; + float Time; + + FVector2D HalfPlane = PlaneSize / 2.f; + FVector2D PtCenter; + FVector2D NewPt; + FVector PlanePoint; + int CenterCount = 0; + for (int i = 0; i < 24; i += 2) + { + + if (UKismetMathLibrary::LinePlaneIntersection(PointList[EdgeList[i]], PointList[EdgeList[i + 1]], LocalProjectionPlane, Time, Intersection)) + { + //DrawDebugSphere(GetWorld(), WorldTransformOfPlane.TransformPosition(Intersection), 2.f, 32.f, FColor::Black); + PlanePoint = Intersection; + + if (IsValid(RenderTarget)) + { + NewPt.X = ((PlanePoint.X + HalfPlane.X) / PlaneSize.X) * RenderTarget->SizeX; + NewPt.Y = ((PlanePoint.Y + HalfPlane.Y) / PlaneSize.Y) * RenderTarget->SizeY; + } + else + { + NewPt.X = ((PlanePoint.X + HalfPlane.X) / PlaneSize.X) * RenderTargetWidth; + NewPt.Y = ((PlanePoint.Y + HalfPlane.Y) / PlaneSize.Y) * RenderTargetHeight; + } + + IntersectionPoints.Add(NewPt); + PtCenter += NewPt; + CenterCount++; + } + } + + if (IntersectionPoints.Num() <= 2) + { + return false; + } + + // Get our center value + PtCenter /= CenterCount; + + // Sort the points clockwise + struct FPointSortCompare + { + public: + FVector2D CenterPoint; + FPointSortCompare(const FVector2D& InCenterPoint) + : CenterPoint(InCenterPoint) + { + + } + + FORCEINLINE bool operator()(const FVector2D& A, const FVector2D& B) const + { + if (A.Y - CenterPoint.X >= 0 && B.X - CenterPoint.X < 0) + return true; + if (A.X - CenterPoint.X < 0 && B.X - CenterPoint.X >= 0) + return false; + if (A.X - CenterPoint.X == 0 && B.X - CenterPoint.X == 0) { + if (A.Y - CenterPoint.Y >= 0 || B.Y - CenterPoint.Y >= 0) + return A.Y > B.Y; + return B.Y > A.Y; + } + + // compute the cross product of vectors (center -> a) x (center -> b) + int det = (A.X - CenterPoint.X) * (B.Y - CenterPoint.Y) - (B.X - CenterPoint.X) * (A.Y - CenterPoint.Y); + if (det < 0) + return true; + if (det > 0) + return false; + + // points a and b are on the same line from the center + // check which point is closer to the center + int d1 = (A.X - CenterPoint.X) * (A.X - CenterPoint.X) + (A.Y - CenterPoint.Y) * (A.Y - CenterPoint.Y); + int d2 = (B.X - CenterPoint.X) * (B.X - CenterPoint.X) + (B.Y - CenterPoint.Y) * (B.Y - CenterPoint.Y); + return d1 > d2; + } + }; + + IntersectionPoints.Sort(FPointSortCompare(PtCenter)); + + FCanvasUVTri Tri; + Tri.V0_Color = UVColor; + Tri.V1_Color = UVColor; + Tri.V2_Color = UVColor; + + OutTris.Reserve(IntersectionPoints.Num() - 2); + + // Now that we have our sorted list, we can generate a tri map from it, just doing a Fan from first to last + for (int i = 1; i < IntersectionPoints.Num() - 1; i++) + { + Tri.V0_Pos = IntersectionPoints[0]; + Tri.V1_Pos = IntersectionPoints[i]; + Tri.V2_Pos = IntersectionPoints[i + 1]; + + OutTris.Add(Tri); + } + + return true; +} + +void FBPVRReplicatedTextureStore::PackData() +{ + if (UnpackedData.Num() > 0) + { + TArray TmpPacked; + RLE_Funcs::RLEEncodeBuffer(UnpackedData.GetData(), UnpackedData.Num(), &TmpPacked); + UnpackedData.Reset(); + + /*if (TmpPacked.Num() > 30000) + { + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); + TSharedPtr imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); + + imageWrapper->SetRaw(UnpackedData.GetData(), UnpackedData.Num(), Width, Height, ERGBFormat::RGBA, 8); + const TArray64& ImgData = imageWrapper->GetCompressed(1); + + + PackedData.Reset(ImgData.Num()); + PackedData.AddUninitialized(ImgData.Num()); + FMemory::Memcpy(PackedData.GetData(), ImgData.GetData(), ImgData.Num()); + bJPG = true; + bIsZipped = false; + } + else */if (TmpPacked.Num() > 512) + { + FArchiveSaveCompressedProxy Compressor(PackedData, NAME_Zlib, COMPRESS_BiasSpeed); + Compressor << TmpPacked; + Compressor.Flush(); + bIsZipped = true; + //bJPG = false; + } + else + { + PackedData = TmpPacked; + bIsZipped = false; + //bJPG = false; + } + } +} + + +void FBPVRReplicatedTextureStore::UnPackData() +{ + if (PackedData.Num() > 0) + { + + /*if (bJPG) + { + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); + TSharedPtr imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); + + + if (imageWrapper.IsValid() && (PackedData.Num() > 0) && imageWrapper->SetCompressed(PackedData.GetData(), PackedData.Num())) + { + Width = imageWrapper->GetWidth(); + Height = imageWrapper->GetHeight(); + + if (imageWrapper->GetRaw(ERGBFormat::BGRA, 8, UnpackedData)) + { + //bSucceeded = true; + } + } + } + else */if (bIsZipped) + { + TArray RLEEncodedData; + FArchiveLoadCompressedProxy DataArchive(PackedData, NAME_Zlib); + DataArchive << RLEEncodedData; + RLE_Funcs::RLEDecodeLine(&RLEEncodedData, &UnpackedData, true); + } + else + { + RLE_Funcs::RLEDecodeLine(&PackedData, &UnpackedData, true); + } + + PackedData.Reset(); + } +} + +/** Network serialization */ +// Doing a custom NetSerialize here because this is sent via RPCs and should change on every update +bool FBPVRReplicatedTextureStore::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + //Ar.SerializeBits(&bIsJPG, 1); + Ar.SerializeBits(&bIsZipped, 1); + Ar.SerializeIntPacked(Width); + Ar.SerializeIntPacked(Height); + Ar.SerializeBits(&PixelFormat, 8); + + Ar << PackedData; + + //uint32 UncompressedBufferSize = PackedData.Num(); + + return bOutSuccess; +} + + +// BEGIN RLE FUNCTIONS /// + +// Followed by a count of the following voxels +template +void RLE_Funcs::RLEDecodeLine(TArray* LineToDecode, TArray* DecodedLine, bool bCompressed) +{ + if (!LineToDecode || !DecodedLine) + return; + + RLEDecodeLine(LineToDecode->GetData(), LineToDecode->Num(), DecodedLine, bCompressed); +} + +// Followed by a count of the following voxels +template +void RLE_Funcs::RLEDecodeLine(const uint8* LineToDecode, uint32 Num, TArray* DecodedLine, bool bCompressed) +{ + if (!bCompressed) + { + DecodedLine->Empty(Num / sizeof(DataType)); + DecodedLine->AddUninitialized(Num / sizeof(DataType)); + FMemory::Memcpy(DecodedLine->GetData(), LineToDecode, Num); + return; + } + + const uint8* StartLoc = LineToDecode; + const uint8* EndLoc = StartLoc + Num; + uint8 incr = sizeof(DataType); + + DataType ValToWrite = *((DataType*)LineToDecode); // This is just to prevent stupid compiler warnings without disabling them + + DecodedLine->Empty(); + + uint8 RLE_FLAG; + uint32 Length32; + uint32 Length8; + uint32 Length16; + int origLoc; + + for (const uint8* loc = StartLoc; loc < EndLoc;) + { + RLE_FLAG = *loc >> 4; // Get the RLE flag from the first 4 bits of the first byte + + switch (RLE_FLAG) + { + case RLE_Flags::RLE_CompressedByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_CompressedShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_Compressed24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | (uint32)(*(loc + 2))) + 1; + loc += 3; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + + case RLE_Flags::RLE_NotCompressedByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + case RLE_Flags::RLE_NotCompressedShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + case RLE_Flags::RLE_NotCompressed24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | ((uint32)(*(loc + 2)))) + 1; + loc += 3; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + + case RLE_Flags::RLE_ContinueRunByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_ContinueRunShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_ContinueRun24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | (*(loc + 2))) + 1; + loc += 3; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + + } + } +} + +template +bool RLE_Funcs::RLEEncodeLine(TArray* LineToEncode, TArray* EncodedLine) +{ + return RLEEncodeBuffer(LineToEncode->GetData(), LineToEncode->Num(), EncodedLine); +} + +void RLE_Funcs::RLEWriteContinueFlag(uint32 count, uint8** loc) +{ + if (count <= 16) + { + **loc = (((uint8)RLE_Flags::RLE_ContinueRunByte << 4) | ((uint8)count - 1)); + (*loc)++; + } + else if (count <= 4096) + { + uint16 val = ((((uint16)RLE_Flags::RLE_ContinueRunShort) << 12) | ((uint16)count - 1)); + **loc = val >> 8; + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } + else + { + uint32 val = ((((uint32)RLE_Flags::RLE_ContinueRun24) << 20) | ((uint32)count - 1)); + **loc = (uint8)(val >> 16); + (*loc)++; + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } +} + +template +void RLE_Funcs::RLEWriteRunFlag(uint32 count, uint8** loc, TArray& Data, bool bCompressed) +{ + + if (count <= 16) + { + uint8 val; + if (bCompressed) + val = ((((uint8)RLE_Flags::RLE_CompressedByte) << 4) | ((uint8)count - 1)); + else + val = ((((uint8)RLE_Flags::RLE_NotCompressedByte) << 4) | ((uint8)count - 1)); + + **loc = val; + (*loc)++; + } + else if (count <= 4096) + { + uint16 val; + if (bCompressed) + val = ((((uint16)RLE_Flags::RLE_CompressedShort) << 12) | ((uint16)count - 1)); + else + val = ((((uint16)RLE_Flags::RLE_NotCompressedShort) << 12) | ((uint16)count - 1)); + + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } + else + { + uint32 val; + if (bCompressed) + val = ((((uint32)RLE_Flags::RLE_Compressed24) << 20) | ((uint32)count - 1)); + else + val = ((((uint32)RLE_Flags::RLE_NotCompressed24) << 20) | ((uint32)count - 1)); + + **loc = (uint8)(val >> 16); + (*loc)++; + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)(val); + (*loc)++; + } + + FMemory::Memcpy(*loc, Data.GetData(), Data.Num() * sizeof(DataType)); + *loc += Data.Num() * sizeof(DataType); + Data.Empty(256); +} + +template +bool RLE_Funcs::RLEEncodeBuffer(DataType* BufferToEncode, uint32 EncodeLength, TArray* EncodedLine) +{ + uint32 OrigNum = EncodeLength;//LineToEncode->Num(); + uint8 incr = sizeof(DataType); + uint32 MAX_COUNT = 1048576; // Max of 2.5 bytes as 0.5 bytes is used for control flags + + EncodedLine->Empty((OrigNum * sizeof(DataType)) + (OrigNum * (sizeof(short)))); + // Reserve enough memory to account for a perfectly bad situation (original size + 3 bytes per max array value) + // Remove the remaining later with RemoveAt() and a count + EncodedLine->AddUninitialized((OrigNum * sizeof(DataType)) + ((OrigNum / MAX_COUNT * 3))); + + DataType* First = BufferToEncode;// LineToEncode->GetData(); + DataType Last; + uint32 RunCount = 0; + + uint8* loc = EncodedLine->GetData(); + //uint8 * countLoc = NULL; + + bool bInRun = false; + bool bWroteStart = false; + bool bContinueRun = false; + + TArray TempBuffer; + TempBuffer.Reserve(256); + uint32 TempCount = 0; + + Last = *First; + First++; + + for (uint32 i = 0; i < OrigNum - 1; i++, First++) + { + if (Last == *First) + { + if (bWroteStart && !bInRun) + { + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + bWroteStart = false; + } + + if (bInRun && /**countLoc*/TempCount < MAX_COUNT) + { + TempCount++; + + if (TempCount == MAX_COUNT) + { + // Write run byte + if (bContinueRun) + { + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + + bContinueRun = true; + TempCount = 0; + } + } + else + { + bInRun = true; + bWroteStart = false; + bContinueRun = false; + + TempBuffer.Add(Last); + TempCount = 1; + } + + // Begin Run Here + } + else if (bInRun) + { + bInRun = false; + + if (bContinueRun) + { + TempCount++; + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + { + TempCount++; + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + } + + bContinueRun = false; + } + else + { + if (bWroteStart && TempCount/**countLoc*/ < MAX_COUNT) + { + TempCount++; + TempBuffer.Add(Last); + } + else if (bWroteStart && TempCount == MAX_COUNT) + { + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + + bWroteStart = true; + TempBuffer.Add(Last); + TempCount = 1; + } + else + { + TempBuffer.Add(Last); + TempCount = 1; + //*countLoc = 1; + + bWroteStart = true; + } + } + + Last = *First; + } + + // Finish last num + if (bInRun) + { + if (TempCount <= MAX_COUNT) + { + if (TempCount == MAX_COUNT) + { + // Write run byte + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + bContinueRun = true; + } + + if (bContinueRun) + { + TempCount++; + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + { + TempCount++; + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + } + } + + // Begin Run Here + } + else + { + if (bWroteStart && TempCount/**countLoc*/ <= MAX_COUNT) + { + if (TempCount/**countLoc*/ == MAX_COUNT) + { + // Write run byte + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + TempCount = 0; + } + + TempCount++; + TempBuffer.Add(Last); + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + } + } + + // Resize the out array to fit compressed contents + uint32 Wrote = loc - EncodedLine->GetData(); + EncodedLine->RemoveAt(Wrote, EncodedLine->Num() - Wrote, true); + + // If the compression performed worse than the original file size, throw the results array and use the original instead. + // This will almost never happen with voxels but can so should be accounted for. + + return true; + // Skipping non compressed for now, the overhead is so low that it isn't worth supporting since the last revision + if (Wrote > OrigNum * incr) + { + EncodedLine->Empty(OrigNum * incr); + EncodedLine->AddUninitialized(OrigNum * incr); + FMemory::Memcpy(EncodedLine->GetData(), BufferToEncode/*LineToEncode->GetData()*/, OrigNum * incr); + return false; // Return that there was no compression, so the decoder can receive it later + } + else + return true; +} + +template +bool WritePackedVector2D(FVector2D Value, FArchive& Ar) // Note Value is intended to not be a reference since we are scaling it before serializing! +{ + check(Ar.IsSaving()); + + // Scale vector by quant factor first + Value *= ScaleFactor; + + // Nan Check + if (Value.ContainsNaN()) + { + logOrEnsureNanError(TEXT("WritePackedVector2D: Value contains NaN, clearing for safety.")); + FVector2D Dummy(0, 0); + WritePackedVector2D(Dummy, Ar); + return false; + } + + float MinV = -1073741824.0f; + float MaxV = 1073741760.0f; + + // Some platforms have RoundToInt implementations that essentially reduces the allowed inputs to 2^31. + const FVector2D ClampedValue = FVector2D(FMath::Clamp(Value.X, MinV, MaxV), FMath::Clamp(Value.Y, MinV, MaxV)); + bool bClamp = ClampedValue != Value; + + // Do basically FVector::SerializeCompressed + int32 IntX = FMath::RoundToInt(ClampedValue.X); + int32 IntY = FMath::RoundToInt(ClampedValue.Y); + + uint32 Bits = FMath::Clamp(FMath::CeilLogTwo(1 + FMath::Max(FMath::Abs(IntX), FMath::Abs(IntY))), 1, MaxBitsPerComponent) - 1; + + // Serialize how many bits each component will have + Ar.SerializeInt(Bits, MaxBitsPerComponent); + + int32 Bias = 1 << (Bits + 1); + uint32 Max = 1 << (Bits + 2); + uint32 DX = IntX + Bias; + uint32 DY = IntY + Bias; + + if (DX >= Max) { bClamp = true; DX = static_cast(DX) > 0 ? Max - 1 : 0; } + if (DY >= Max) { bClamp = true; DY = static_cast(DY) > 0 ? Max - 1 : 0; } + + Ar.SerializeInt(DX, Max); + Ar.SerializeInt(DY, Max); + + return !bClamp; +} + +template +bool ReadPackedVector2D(FVector2D& Value, FArchive& Ar) +{ + uint32 Bits = 0; + + // Serialize how many bits each component will have + Ar.SerializeInt(Bits, MaxBitsPerComponent); + + int32 Bias = 1 << (Bits + 1); + uint32 Max = 1 << (Bits + 2); + uint32 DX = 0; + uint32 DY = 0; + + Ar.SerializeInt(DX, Max); + Ar.SerializeInt(DY, Max); + + + float fact = (float)ScaleFactor; + + Value.X = (float)(static_cast(DX) - Bias) / fact; + Value.Y = (float)(static_cast(DY) - Bias) / fact; + + return true; +} + +bool FRenderManagerOperation::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + + Ar.SerializeIntPacked(OwnerID); + Ar.SerializeBits(&OperationType, 3); + + switch (OperationType) + { + case ERenderManagerOperationType::Op_LineDraw: + { + Ar << Color; + Ar.SerializeIntPacked(Thickness); + + if (Ar.IsSaving()) + { + bOutSuccess &= WritePackedVector2D<1, 20>(P1, Ar); + bOutSuccess &= WritePackedVector2D<1, 20>(P2, Ar); + } + else + { + ReadPackedVector2D<1, 20>(P1, Ar); + ReadPackedVector2D<1, 20>(P2, Ar); + } + }break; + case ERenderManagerOperationType::Op_TexDraw: + { + Ar << Texture; + + if (Ar.IsSaving()) + { + bOutSuccess &= WritePackedVector2D<1, 20>(P1, Ar); + } + else + { + ReadPackedVector2D<1, 20>(P1, Ar); + } + }break; + case ERenderManagerOperationType::Op_TriDraw: + { + Ar << Color; + Ar << Material; + + uint32 ArrayCt = Tris.Num(); + Ar.SerializeIntPacked(ArrayCt); + + if (Ar.IsLoading()) + { + Tris.Reset(ArrayCt); + Tris.AddUninitialized(ArrayCt); + + FRenderManagerTri TriTemp; + for (uint32 i = 0; i < ArrayCt; ++i) + { + ReadPackedVector2D<1, 20>(TriTemp.P1, Ar); + ReadPackedVector2D<1, 20>(TriTemp.P2, Ar); + ReadPackedVector2D<1, 20>(TriTemp.P3, Ar); + Tris[i] = TriTemp; + } + } + else + { + for (uint32 i = 0; i < ArrayCt; ++i) + { + WritePackedVector2D<1, 20>(Tris[i].P1, Ar); + WritePackedVector2D<1, 20>(Tris[i].P2, Ar); + WritePackedVector2D<1, 20>(Tris[i].P3, Ar); + } + } + + }break; + } + + return bOutSuccess; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp new file mode 100644 index 0000000..4dbb9ed --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp @@ -0,0 +1,256 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "ParentRelativeAttachmentComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(ParentRelativeAttachmentComponent) + +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "IXRTrackingSystem.h" +#include "VRRootComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" +//#include "VRSimpleCharacter.h" +//#include "VRCharacter.h" + + +UParentRelativeAttachmentComponent::UParentRelativeAttachmentComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + // Let it sit in DuringPhysics like is the default + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + + bWantsInitializeComponent = true; + + SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); + SetRelativeLocation(FVector::ZeroVector); + YawTolerance = 0.0f; + //bOffsetByHMD = false; + + bLerpTransition = true; + LerpSpeed = 100.0f; + LastLerpVal = 0.0f; + LerpTarget = 0.0f; + bWasSetOnce = false; + + LeftControllerTrans = FTransform::Identity; + RightControllerTrans = FTransform::Identity; + + bIgnoreRotationFromParent = false; + bUpdateInCharacterMovement = true; + bIsPaused = false; + + CustomOffset = FVector::ZeroVector; + + //YawRotationMethod = EVR_PRC_RotationMethod::PRC_ROT_HMD; +} + +void UParentRelativeAttachmentComponent::InitializeComponent() +{ + Super::InitializeComponent(); + + // Update our tracking + if (!bUseFeetLocation && IsValid(AttachChar)) // New case to early out and with less calculations + { + SetRelativeTransform(AttachChar->VRReplicatedCamera->GetRelativeTransform()); + } + +} + +void UParentRelativeAttachmentComponent::OnAttachmentChanged() +{ + if (AVRCharacter* CharacterOwner = Cast(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + if (AVRBaseCharacter * BaseCharacterOwner = Cast(this->GetOwner())) + { + AttachBaseChar = BaseCharacterOwner; + } + else + { + AttachBaseChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +void UParentRelativeAttachmentComponent::SetPaused(bool bNewPaused, bool bZeroOutRotation, bool bZeroOutLocation) +{ + if (bNewPaused != bIsPaused) + { + bIsPaused = bNewPaused; + + if (bIsPaused && (bZeroOutLocation || bZeroOutRotation)) + { + FVector NewLoc = this->GetRelativeLocation(); + FRotator NewRot = this->GetRelativeRotation(); + + if (bZeroOutLocation) + { + NewLoc = FVector::ZeroVector; + } + + if (bZeroOutRotation) + { + NewRot = FRotator::ZeroRotator; + } + + SetRelativeLocationAndRotation(NewLoc, NewRot); + } + } +} + +void UParentRelativeAttachmentComponent::SetRelativeRotAndLoc(FVector NewRelativeLocation, FRotator NewRelativeRotation, float DeltaTime) +{ + + RunSampling(NewRelativeRotation, NewRelativeLocation); + + if (bUseFeetLocation) + { + FVector TotalOffset = CustomOffset; + if (bUseCenterAsFeetLocation && AttachChar && AttachChar->VRRootReference) + { + TotalOffset.Z += AttachChar->VRRootReference->GetUnscaledCapsuleHalfHeight(); + } + + if (!bIgnoreRotationFromParent) + { + SetRelativeLocationAndRotation( + FVector(NewRelativeLocation.X, NewRelativeLocation.Y, 0.0f) + TotalOffset, + GetCalculatedRotation(NewRelativeRotation, DeltaTime) + ); + } + else + { + SetRelativeLocation(FVector(NewRelativeLocation.X, NewRelativeLocation.Y, 0.0f) + TotalOffset); + } + } + else + { + if (!bIgnoreRotationFromParent) + { + SetRelativeLocationAndRotation( + NewRelativeLocation + CustomOffset, + GetCalculatedRotation(NewRelativeRotation, DeltaTime) + ); // Use the HMD height instead + } + else + { + SetRelativeLocation(NewRelativeLocation + CustomOffset); // Use the HMD height instead + } + } +} + +void UParentRelativeAttachmentComponent::UpdateTracking(float DeltaTime) +{ + // We are paused, do not update tracking anymore + if (bIsPaused) + return; + + if (OptionalWaistTrackingParent.IsValid()) + { + //#TODO: bOffsetByHMD not supported with this currently, fix it, need to check for both camera and HMD + FTransform TrackedParentWaist = IVRTrackedParentInterface::Default_GetWaistOrientationAndPosition(OptionalWaistTrackingParent); + + if (bUseFeetLocation) + { + TrackedParentWaist.SetTranslation(TrackedParentWaist.GetTranslation() * FVector(1.0f, 1.0f, 0.0f)); + + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(TrackedParentWaist.Rotator()); + + TrackedParentWaist.SetRotation(GetCalculatedRotation(InverseRot, DeltaTime)); + } + } + + TrackedParentWaist.AddToTranslation(CustomOffset); + SetRelativeTransform(TrackedParentWaist); + + } + else if (IsValid(AttachChar)) // New case to early out and with less calculations + { + if (AttachChar->bRetainRoomscale) + { + SetRelativeRotAndLoc(AttachChar->VRRootReference->curCameraLoc, AttachChar->VRRootReference->StoredCameraRotOffset, DeltaTime); + } + else + { + FVector CameraLoc = FVector(0.0f, 0.0f, AttachChar->VRRootReference->curCameraLoc.Z); + CameraLoc += AttachChar->VRRootReference->StoredCameraRotOffset.RotateVector(FVector(-AttachChar->VRRootReference->VRCapsuleOffset.X, -AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + SetRelativeRotAndLoc(CameraLoc, AttachChar->VRRootReference->StoredCameraRotOffset, DeltaTime); + } + } + else if (IsLocallyControlled() && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed()) + { + FQuat curRot; + FVector curCameraLoc; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc)) + { + /*if (bOffsetByHMD) + { + curCameraLoc.X = 0; + curCameraLoc.Y = 0; + }*/ + + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curRot.Rotator()); + SetRelativeRotAndLoc(curCameraLoc, InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(curCameraLoc, FRotator::ZeroRotator, DeltaTime); + } + } + else if (IsValid(AttachBaseChar)) + { + if (AttachBaseChar->VRReplicatedCamera) + { + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw(AttachBaseChar->VRReplicatedCamera->GetRelativeRotation()); + SetRelativeRotAndLoc(AttachBaseChar->VRReplicatedCamera->GetRelativeLocation(), InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(AttachBaseChar->VRReplicatedCamera->GetRelativeLocation(), FRotator::ZeroRotator, DeltaTime); + } + } + else if (AActor* owner = this->GetOwner()) + { + if (UCameraComponent* CameraOwner = owner->FindComponentByClass()) + { + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw(CameraOwner->GetRelativeRotation()); + SetRelativeRotAndLoc(CameraOwner->GetRelativeLocation(), InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(CameraOwner->GetRelativeLocation(), FRotator::ZeroRotator, DeltaTime); + } + } +} + +void UParentRelativeAttachmentComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!bUpdateInCharacterMovement || !IsValid(AttachChar)) + { + UpdateTracking(DeltaTime); + } + else + { + UCharacterMovementComponent * CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive()) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp new file mode 100644 index 0000000..38c5174 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp @@ -0,0 +1,573 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "ReplicatedVRCameraComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(ReplicatedVRCameraComponent) + +#include "Net/UnrealNetwork.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "VRRootComponent.h" +#include "IXRTrackingSystem.h" +#include "IXRCamera.h" +#include "Rendering/MotionVectorSimulation.h" + + +UReplicatedVRCameraComponent::UReplicatedVRCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + + SetIsReplicatedByDefault(true); + SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); + + // Default 100 htz update rate, same as the 100htz update rate of rep_notify, will be capped to 90/45 though because of vsync on HMD + //bReplicateTransform = true; + NetUpdateRate = 100.0f; // 100 htz is default + NetUpdateCount = 0.0f; + + bUsePawnControlRotation = false; + bAutoSetLockToHmd = true; + bScaleTracking = false; + TrackingScaler = FVector(1.0f); + //bOffsetByHMD = false; + bLimitMinHeight = false; + MinimumHeightAllowed = 0.0f; + bLimitMaxHeight = false; + MaxHeightAllowed = 300.f; + bLimitBounds = false; + // Just shy of 20' distance from the center of tracked space + MaximumTrackedBounds = 1028; + + bSetPositionDuringTick = false; + bSmoothReplicatedMotion = false; + bLerpingPosition = false; + bReppedOnce = false; + + OverrideSendTransform = nullptr; + + LastRelativePosition = FTransform::Identity; + bSampleVelocityInWorldSpace = false; + bHadValidFirstVelocity = false; + + //bUseVRNeckOffset = true; + //VRNeckOffset = FTransform(FRotator::ZeroRotator, FVector(15.0f,0,0), FVector(1.0f)); + +} + + +//============================================================================= +void UReplicatedVRCameraComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + + // I am skipping the Scene component replication here + // Generally components aren't set to replicate anyway and I need it to NOT pass the Relative position through the network + // There isn't much in the scene component to replicate anyway + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + // Skipping the owner with this as the owner will use the location directly + DOREPLIFETIME_CONDITION(UReplicatedVRCameraComponent, ReplicatedCameraTransform, COND_SkipOwner); + DOREPLIFETIME(UReplicatedVRCameraComponent, NetUpdateRate); + DOREPLIFETIME(UReplicatedVRCameraComponent, bSmoothReplicatedMotion); + //DOREPLIFETIME(UReplicatedVRCameraComponent, bReplicateTransform); +} + +// Just skipping this, it generates warnings for attached meshes when using this method of denying transform replication +/*void UReplicatedVRCameraComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeLocation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeRotation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeScale3D, false); +}*/ + +void UReplicatedVRCameraComponent::Server_SendCameraTransform_Implementation(FBPVRComponentPosRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedCameraTransform = NewTransform; + + // Don't call on rep on the server if the server controls this controller + if (!bHasAuthority) + { + OnRep_ReplicatedCameraTransform(); + } +} + +bool UReplicatedVRCameraComponent::Server_SendCameraTransform_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +/*bool UReplicatedVRCameraComponent::IsServer() +{ + if (GEngine != nullptr && GWorld != nullptr) + { + switch (GEngine->GetNetMode(GWorld)) + { + case NM_Client: + {return false; } break; + case NM_DedicatedServer: + case NM_ListenServer: + default: + {return true; } break; + } + } + + return false; +}*/ + +void UReplicatedVRCameraComponent::OnAttachmentChanged() +{ + if (AVRCharacter* CharacterOwner = Cast(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +bool UReplicatedVRCameraComponent::HasTrackingParameters() +{ + return /*bOffsetByHMD ||*/ bScaleTracking || bLimitMaxHeight || bLimitMinHeight || bLimitBounds || (AttachChar && !AttachChar->bRetainRoomscale); +} + +void UReplicatedVRCameraComponent::ApplyTrackingParameters(FVector &OriginalPosition, bool bSkipLocZero) +{ + // I'm keeping the original values here as it lets me send them out for seated mode + if (!bSkipLocZero /*&& ((AttachChar && !AttachChar->bRetainRoomscale))*/) + { + OriginalPosition.X = 0; + OriginalPosition.Y = 0; + } + + if (bLimitBounds) + { + OriginalPosition.X = FMath::Clamp(OriginalPosition.X, -MaximumTrackedBounds, MaximumTrackedBounds); + OriginalPosition.Y = FMath::Clamp(OriginalPosition.Y, -MaximumTrackedBounds, MaximumTrackedBounds); + } + + if (bScaleTracking) + { + OriginalPosition *= TrackingScaler; + } + + if (bLimitMaxHeight) + { + OriginalPosition.Z = FMath::Min(MaxHeightAllowed, OriginalPosition.Z); + } + + if (bLimitMinHeight) + { + OriginalPosition.Z = FMath::Max(MinimumHeightAllowed, OriginalPosition.Z); + } +} + +void UReplicatedVRCameraComponent::UpdateTracking(float DeltaTime) +{ + bHasAuthority = IsLocallyControlled(); + + // Don't do any of the below if we aren't the authority + if (bHasAuthority) + { + // For non view target positional updates (third party and the like) + if (bSetPositionDuringTick && bLockToHmd && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + //ResetRelativeTransform(); + FQuat Orientation; + FVector Position; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, Orientation, Position)) + { + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position); + } + + ReplicatedCameraTransform.Position = Position; + ReplicatedCameraTransform.Rotation = Orientation.Rotator(); + + if (IsValid(AttachChar) && !AttachChar->bRetainRoomscale) + { + // Zero out camera posiiton + Position.X = 0.0f; + Position.Y = 0.0f; + + FRotator StoredCameraRotOffset = FRotator::ZeroRotator; + if (AttachChar->VRMovementReference->GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + AttachChar->SeatInformation.InitialRelCameraTransform.Rotator(); + } + else + { + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(Orientation.Rotator()); + } + + Position += StoredCameraRotOffset.RotateVector(FVector(-AttachChar->VRRootReference->VRCapsuleOffset.X, -AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + + } + + SetRelativeTransform(FTransform(Orientation, Position)); + } + } + } + else + { + // Run any networked smoothing + RunNetworkedSmoothing(DeltaTime); + } + + // Save out the component velocity from this and last frame + if(bHadValidFirstVelocity || !LastRelativePosition.Equals(FTransform::Identity)) + { + bHadValidFirstVelocity = true; + ComponentVelocity = ((bSampleVelocityInWorldSpace ? GetComponentLocation() : GetRelativeLocation()) - LastRelativePosition.GetTranslation()) / DeltaTime; + } + + LastRelativePosition = bSampleVelocityInWorldSpace ? this->GetComponentTransform() : this->GetRelativeTransform(); +} + +void UReplicatedVRCameraComponent::RunNetworkedSmoothing(float DeltaTime) +{ + FVector RetainPositionOffset(0.0f, 0.0f, ReplicatedCameraTransform.Position.Z); + + if (AttachChar && !AttachChar->bRetainRoomscale) + { + FRotator StoredCameraRotOffset = FRotator::ZeroRotator; + if (AttachChar->VRMovementReference->GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + AttachChar->SeatInformation.InitialRelCameraTransform.Rotator(); + } + else + { + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(ReplicatedCameraTransform.Rotation); + } + + RetainPositionOffset += StoredCameraRotOffset.RotateVector(FVector(-AttachChar->VRRootReference->VRCapsuleOffset.X, -AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + + if (bLerpingPosition) + { + if (!bUseExponentialSmoothing) + { + NetUpdateCount += DeltaTime; + float LerpVal = FMath::Clamp(NetUpdateCount / (1.0f / NetUpdateRate), 0.0f, 1.0f); + + if (LerpVal >= 1.0f) + { + if (AttachChar && !AttachChar->bRetainRoomscale) + { + SetRelativeLocationAndRotation(RetainPositionOffset, ReplicatedCameraTransform.Rotation); + } + else + { + SetRelativeLocationAndRotation(ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + } + + // Stop lerping, wait for next update if it is delayed or lost then it will hitch here + // Actual prediction might be something to consider in the future, but rough to do in VR + // considering the speed and accuracy of movements + // would like to consider sub stepping but since there is no server rollback...not sure how useful it would be + // and might be perf taxing enough to not make it worth it. + bLerpingPosition = false; + NetUpdateCount = 0.0f; + } + else + { + + if (AttachChar && !AttachChar->bRetainRoomscale) + { + // Removed variables to speed this up a bit + SetRelativeLocationAndRotation( + FMath::Lerp(LastUpdatesRelativePosition, RetainPositionOffset, LerpVal), + FMath::Lerp(LastUpdatesRelativeRotation, ReplicatedCameraTransform.Rotation, LerpVal) + ); + } + else + { + // Removed variables to speed this up a bit + SetRelativeLocationAndRotation( + FMath::Lerp(LastUpdatesRelativePosition, (FVector)ReplicatedCameraTransform.Position, LerpVal), + FMath::Lerp(LastUpdatesRelativeRotation, ReplicatedCameraTransform.Rotation, LerpVal) + ); + } + } + } + else // Exponential Smoothing + { + if (InterpolationSpeed <= 0.f) + { + if (AttachChar && !AttachChar->bRetainRoomscale) + { + SetRelativeLocationAndRotation(RetainPositionOffset, ReplicatedCameraTransform.Rotation); + } + else + { + SetRelativeLocationAndRotation((FVector)ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + } + + bLerpingPosition = false; + return; + } + + const float Alpha = FMath::Clamp(DeltaTime * InterpolationSpeed, 0.f, 1.f); + + FTransform NA = FTransform(GetRelativeRotation(), GetRelativeLocation(), FVector(1.0f)); + FTransform NB = FTransform::Identity; + + if (AttachChar && !AttachChar->bRetainRoomscale) + { + NB = FTransform(ReplicatedCameraTransform.Rotation, RetainPositionOffset, FVector(1.0f)); + } + else + { + NB = FTransform(ReplicatedCameraTransform.Rotation, (FVector)ReplicatedCameraTransform.Position, FVector(1.0f)); + } + + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + NA.Blend(NA, NB, Alpha); + + // If we are nearly equal then snap to final position + if (NA.EqualsNoScale(NB)) + { + if (AttachChar && !AttachChar->bRetainRoomscale) + { + SetRelativeLocationAndRotation(RetainPositionOffset, ReplicatedCameraTransform.Rotation); + } + else + { + SetRelativeLocationAndRotation(ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + } + } + else // Else just keep going + { + SetRelativeLocationAndRotation(NA.GetTranslation(), NA.Rotator()); + } + } + } +} + + +void UReplicatedVRCameraComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + if (!bUpdateInCharacterMovement || !IsValid(AttachChar)) + { + UpdateTracking(DeltaTime); + } + else + { + UCharacterMovementComponent* CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive() || (!CharMove->PrimaryComponentTick.bTickEvenWhenPaused && GetWorld()->IsPaused())) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + } + + if (bHasAuthority) + { + // Send changes + if (this->GetIsReplicated()) + { + FRotator RelativeRot = GetRelativeRotation(); + FVector RelativeLoc = GetRelativeLocation(); + + // Don't rep if no changes + if (!RelativeLoc.Equals(LastUpdatesRelativePosition) || !RelativeRot.Equals(LastUpdatesRelativeRotation)) + { + NetUpdateCount += DeltaTime; + + if (NetUpdateCount >= (1.0f / NetUpdateRate)) + { + NetUpdateCount = 0.0f; + + // Already stored out now, only do this for FPS debug characters + if (bFPSDebugMode) + { + ReplicatedCameraTransform.Position = RelativeLoc; + ReplicatedCameraTransform.Rotation = RelativeRot; + } + + if (GetNetMode() == NM_Client) + { + AVRBaseCharacter* OwningChar = Cast(GetOwner()); + if (OverrideSendTransform != nullptr && OwningChar != nullptr) + { + (OwningChar->* (OverrideSendTransform))(ReplicatedCameraTransform); + } + else + { + // Don't bother with any of this if not replicating transform + //if (bHasAuthority && bReplicateTransform) + Server_SendCameraTransform(ReplicatedCameraTransform); + } + } + + LastUpdatesRelativeRotation = RelativeRot; + LastUpdatesRelativePosition = RelativeLoc; + } + } + } + } +} + +void UReplicatedVRCameraComponent::HandleXRCamera() +{ + bool bIsLocallyControlled = IsLocallyControlled(); + + if (bAutoSetLockToHmd) + { + if (bIsLocallyControlled) + bLockToHmd = true; + else + bLockToHmd = false; + } + + if (bIsLocallyControlled && GEngine && GEngine->XRSystem.IsValid() && GetWorld() && GetWorld()->WorldType != EWorldType::Editor) + { + IXRTrackingSystem* XRSystem = GEngine->XRSystem.Get(); + auto XRCamera = XRSystem->GetXRCamera(); + + if (XRCamera.IsValid()) + { + if (XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + const FTransform ParentWorld = CalcNewComponentToWorld(FTransform()); + XRCamera->SetupLateUpdate(ParentWorld, this, bLockToHmd == 0); + + if (bLockToHmd) + { + FQuat Orientation; + FVector Position; + if (XRCamera->UpdatePlayerCamera(Orientation, Position)) + { + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position); + } + + ReplicatedCameraTransform.Position = Position; + ReplicatedCameraTransform.Rotation = Orientation.Rotator(); + + if (IsValid(AttachChar) && !AttachChar->bRetainRoomscale) + { + // Zero out XY for non retained + Position.X = 0.0f; + Position.Y = 0.0f; + //FRotator OffsetRotator = + if (AttachChar->VRMovementReference->GetReplicatedMovementMode() != EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + AttachChar->SeatInformation.InitialRelCameraTransform.Rotator(); + + FRotator StoredCameraRotOffset = FRotator::ZeroRotator; + if (AttachChar->VRMovementReference->GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + AttachChar->SeatInformation.InitialRelCameraTransform.Rotator(); + } + else + { + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(Orientation.Rotator()); + } + + Position += StoredCameraRotOffset.RotateVector(FVector(-AttachChar->VRRootReference->VRCapsuleOffset.X, -AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + } + + SetRelativeTransform(FTransform(Orientation, Position)); + } + else + { + SetRelativeScale3D(FVector(1.0f)); + //ResetRelativeTransform(); stop doing this, it is problematic + // Let the camera freeze in the last position instead + // Setting scale by itself makes sure we don't get camera scaling but keeps the last location and rotation alive + } + } + + // #TODO: Check back on this, was moved here in 4.20 but shouldn't it be inside of bLockToHMD? + XRCamera->OverrideFOV(this->FieldOfView); + } + } + } +} + +void UReplicatedVRCameraComponent::OnRep_ReplicatedCameraTransform() +{ + if (GetNetMode() < ENetMode::NM_Client && HasTrackingParameters()) + { + // Ensure that we clamp to the expected values from the client + ApplyTrackingParameters(ReplicatedCameraTransform.Position, true); + } + + FVector CameraPosition = ReplicatedCameraTransform.Position; + if (AttachChar && !AttachChar->bRetainRoomscale) + { + CameraPosition.X = 0; + CameraPosition.Y = 0; + + FRotator StoredCameraRotOffset = FRotator::ZeroRotator; + if (AttachChar->VRMovementReference->GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + AttachChar->SeatInformation.InitialRelCameraTransform.Rotator(); + } + else + { + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(ReplicatedCameraTransform.Rotation); + } + + CameraPosition += StoredCameraRotOffset.RotateVector(FVector(-AttachChar->VRRootReference->VRCapsuleOffset.X, -AttachChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + + } + + if (bSmoothReplicatedMotion) + { + if (bReppedOnce) + { + bLerpingPosition = true; + NetUpdateCount = 0.0f; + LastUpdatesRelativePosition = this->GetRelativeLocation(); + LastUpdatesRelativeRotation = this->GetRelativeRotation(); + + if (bUseExponentialSmoothing) + { + FVector OldToNewVector = CameraPosition - LastUpdatesRelativePosition; + float NewDistance = OldToNewVector.SizeSquared(); + + // Too far, snap to the new value + if (NewDistance >= FMath::Square(NetworkNoSmoothUpdateDistance)) + { + SetRelativeLocationAndRotation(CameraPosition, ReplicatedCameraTransform.Rotation); + bLerpingPosition = false; + } + // Outside of the buffer distance, snap within buffer and keep smoothing from there + else if (NewDistance >= FMath::Square(NetworkMaxSmoothUpdateDistance)) + { + FVector Offset = (OldToNewVector.Size() - NetworkMaxSmoothUpdateDistance) * OldToNewVector.GetSafeNormal(); + SetRelativeLocation(LastUpdatesRelativePosition + Offset); + } + } + } + else + { + SetRelativeLocationAndRotation(CameraPosition, ReplicatedCameraTransform.Rotation); + bReppedOnce = true; + } + } + else + SetRelativeLocationAndRotation(CameraPosition, ReplicatedCameraTransform.Rotation); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp new file mode 100644 index 0000000..0d770db --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp @@ -0,0 +1,142 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRAIController.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRAIController) + +//#include "VRBPDatatypes.h" +#include "VRBaseCharacter.h" +#include "Components/CapsuleComponent.h" +#include "NetworkingDistanceConstants.h" // Needed for the LinOfSightTo function override to work +#include "Navigation/CrowdFollowingComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + + +FVector AVRAIController::GetFocalPointOnActor(const AActor *Actor) const +{ + if (const AVRBaseCharacter * VRChar = Cast(Actor)) + { + return VRChar->GetVRLocation_Inline(); + } + else + return Super::GetFocalPointOnActor(Actor); // Actor != nullptr ? Actor->GetActorLocation() : FAISystem::InvalidLocation; +} + +bool AVRAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const +{ + if (Other == nullptr) + { + return false; + } + + if (ViewPoint.IsZero()) + { + FRotator ViewRotation; + GetActorEyesViewPoint(ViewPoint, ViewRotation); + + // if we still don't have a view point we simply fail + if (ViewPoint.IsZero()) + { + return false; + } + } + + static FName NAME_LineOfSight = FName(TEXT("LineOfSight")); + FVector TargetLocation = Other->GetTargetLocation(GetPawn()); + + FCollisionQueryParams CollisionParams(NAME_LineOfSight, true, this->GetPawn()); + CollisionParams.AddIgnoredActor(Other); + + bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + + // if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI) + // then don't go any further as it likely will not be tracing to the correct location + const APawn * OtherPawn = Cast(Other); + if (!OtherPawn && Cast(Other->GetRootComponent()) == NULL) + { + return false; + } + + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast(Other); + const FVector OtherActorLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : Other->GetActorLocation(); + + const FVector::FReal DistSq = (OtherActorLocation - ViewPoint).SizeSquared(); + if (DistSq > FARSIGHTTHRESHOLDSQUARED) + { + return false; + } + + if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED)) + { + return false; + } + + float OtherRadius, OtherHeight; + Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight); + + if (!bAlternateChecks || !bLOSflag) + { + //try viewpoint to head + bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + } + + if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag)) + { + // only check sides if width of other is significant compared to distance + if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f) + { + return false; + } + //try checking sides - look at dist to four side points, and cull furthest and closest + FVector Points[4]; + Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0); + Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0); + Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0); + Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0); + int32 IndexMin = 0; + int32 IndexMax = 0; + FVector::FReal CurrentMax = (Points[0] - ViewPoint).SizeSquared(); + FVector::FReal CurrentMin = CurrentMax; + for (int32 PointIndex = 1; PointIndex < 4; PointIndex++) + { + const FVector::FReal NextSize = (Points[PointIndex] - ViewPoint).SizeSquared(); + if (NextSize > CurrentMin) + { + CurrentMin = NextSize; + IndexMax = PointIndex; + } + else if (NextSize < CurrentMax) + { + CurrentMax = NextSize; + IndexMin = PointIndex; + } + } + + for (int32 PointIndex = 0; PointIndex < 4; PointIndex++) + { + if ((PointIndex != IndexMin) && (PointIndex != IndexMax)) + { + bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + } + } + } + return false; +} + +AVRDetourCrowdAIController::AVRDetourCrowdAIController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(TEXT("PathFollowingComponent"))) +{ + +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp new file mode 100644 index 0000000..01921b2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp @@ -0,0 +1,220 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRBPDatatypes.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRBPDatatypes) + +#include "Chaos/ChaosEngineInterface.h" + +namespace VRDataTypeCVARs +{ + // Doing it this way because I want as little rep and perf impact as possible and sampling a static var is that. + // This is to specifically help out very rare cases, DON'T USE THIS UNLESS YOU HAVE NO CHOICE + static int32 RepHighPrecisionTransforms = 0; + FAutoConsoleVariableRef CVarRepHighPrecisionTransforms( + TEXT("vrexp.RepHighPrecisionTransforms"), + RepHighPrecisionTransforms, + TEXT("When on, will rep Quantized transforms at full precision, WARNING use at own risk, if this isn't the same setting client & server then it will crash.\n") + TEXT("0: Disable, 1: Enable"), + ECVF_Default); +} + +bool FTransform_NetQuantize::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + FVector rTranslation; + FVector rScale3D; + //FQuat rRotation; + FRotator rRotation; + + uint16 ShortPitch = 0; + uint16 ShortYaw = 0; + uint16 ShortRoll = 0; + + bool bUseHighPrecision = VRDataTypeCVARs::RepHighPrecisionTransforms > 0; + + if (Ar.IsSaving()) + { + // Because transforms can be vectorized or not, need to use the inline retrievers + rTranslation = this->GetTranslation(); + rScale3D = this->GetScale3D(); + rRotation = this->Rotator();//this->GetRotation(); + + if (bUseHighPrecision) + { + Ar << rTranslation; + Ar << rScale3D; + Ar << rRotation; + } + else + { + // Translation set to 2 decimal precision + bOutSuccess &= SerializePackedVector<100, 30>(rTranslation, Ar); + + // Scale set to 2 decimal precision, had it 1 but realized that I used two already even + bOutSuccess &= SerializePackedVector<100, 30>(rScale3D, Ar); + + // Rotation converted to FRotator and short compressed, see below for conversion reason + // FRotator already serializes compressed short by default but I can save a func call here + rRotation.SerializeCompressedShort(Ar); + } + + + //Ar << rRotation; + + // If I converted it to a rotator and serialized as shorts I would save 6 bytes. + // I am unsure about a safe method of compressed serializing a quat, though I read through smallest three + // Epic already drops W from the Quat and reconstructs it after the send. + // Converting to rotator first may have conversion issues and has a perf cost....needs testing, epic attempts to handle + // Singularities in their conversion but I haven't tested it in all circumstances + //rRotation.SerializeCompressedShort(Ar); + } + else // If loading + { + + if (bUseHighPrecision) + { + Ar << rTranslation; + Ar << rScale3D; + Ar << rRotation; + } + else + { + bOutSuccess &= SerializePackedVector<100, 30>(rTranslation, Ar); + bOutSuccess &= SerializePackedVector<100, 30>(rScale3D, Ar); + rRotation.SerializeCompressedShort(Ar); + } + + //Ar << rRotation; + + // Set it + this->SetComponents(rRotation.Quaternion(), rTranslation, rScale3D); + this->NormalizeRotation(); + } + + return bOutSuccess; +} + +// ** Euro Low Pass Filter ** // + +void FBPEuroLowPassFilter::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FVector FBPEuroLowPassFilter::RunFilterSmoothing(const FVector &InRawValue, const float &InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + // Calculate the delta, if this is the first time then there is no delta + const FVector Delta = RawFilter.bFirstTime == true ? FVector::ZeroVector : (InRawValue - RawFilter.PreviousRaw) * 1.0f / InDeltaTime; + + // Filter the delta to get the estimated + const FVector Estimated = DeltaFilter.Filter(Delta, FVector(DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime))); + + // Use the estimated to calculate the cutoff + const FVector Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + // Filter passed value + return RawFilter.Filter(InRawValue, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)); +} + +void FBPEuroLowPassFilterQuat::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FQuat FBPEuroLowPassFilterQuat::RunFilterSmoothing(const FQuat& InRawValue, const float& InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + FQuat NewInVal = InRawValue; + if (!RawFilter.bFirstTime) + { + // fix axial flipping, from unity open 1 Euro implementation + FVector4 PrevQuatAsVector(RawFilter.Previous.X, RawFilter.Previous.Y, RawFilter.Previous.Z, RawFilter.Previous.W); + FVector4 CurrQuatAsVector(InRawValue.X, InRawValue.Y, InRawValue.Z, InRawValue.W); + if ((PrevQuatAsVector - CurrQuatAsVector).SizeSquared() > 2) + NewInVal = FQuat(-InRawValue.X, -InRawValue.Y, -InRawValue.Z, -InRawValue.W); + } + + // Calculate the delta, if this is the first time then there is no delta + FQuat Delta = FQuat::Identity; + + if (!RawFilter.bFirstTime) + { + Delta = (NewInVal - RawFilter.PreviousRaw) * (1.0f / InDeltaTime); + } + + + float AlphaTau = DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime); + FQuat AlphaTauQ(AlphaTau, AlphaTau, AlphaTau, AlphaTau); + const FQuat Estimated = DeltaFilter.Filter(Delta, AlphaTauQ); + + // Use the estimated to calculate the cutoff + const FQuat Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + // Filter passed value + return RawFilter.Filter(NewInVal, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)).GetNormalized();// .GetNormalized(); +} + +void FBPEuroLowPassFilterTrans::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FTransform FBPEuroLowPassFilterTrans::RunFilterSmoothing(const FTransform& InRawValue, const float& InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + FTransform NewInVal = InRawValue; + if (!RawFilter.bFirstTime) + { + FQuat TransQ = NewInVal.GetRotation(); + FQuat PrevQ = RawFilter.Previous.GetRotation(); + // fix axial flipping, from unity open 1 Euro implementation + FVector4 PrevQuatAsVector(PrevQ.X, PrevQ.Y, PrevQ.Z, PrevQ.W); + FVector4 CurrQuatAsVector(TransQ.X, TransQ.Y, TransQ.Z, TransQ.W); + if ((PrevQuatAsVector - CurrQuatAsVector).SizeSquared() > 2) + NewInVal.SetRotation(FQuat(-TransQ.X, -TransQ.Y, -TransQ.Z, -TransQ.W)); + } + + // Calculate the delta, if this is the first time then there is no delta + FTransform Delta = FTransform::Identity; + + float Frequency = 1.0f / InDeltaTime; + if (!RawFilter.bFirstTime) + { + Delta.SetLocation((NewInVal.GetLocation() - RawFilter.PreviousRaw.GetLocation()) * Frequency); + Delta.SetRotation((NewInVal.GetRotation() - RawFilter.PreviousRaw.GetRotation()) * Frequency); + Delta.SetScale3D((NewInVal.GetScale3D() - RawFilter.PreviousRaw.GetScale3D()) * Frequency); + } + + + float AlphaTau = DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime); + FTransform AlphaTauQ(FQuat(AlphaTau, AlphaTau, AlphaTau, AlphaTau), FVector(AlphaTau), FVector(AlphaTau)); + const FTransform Estimated = DeltaFilter.Filter(Delta, AlphaTauQ); + + // Use the estimated to calculate the cutoff + const FTransform Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + FTransform NewTrans = RawFilter.Filter(NewInVal, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)); + NewTrans.NormalizeRotation(); + // Filter passed value + return NewTrans; +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp new file mode 100644 index 0000000..6b81d1c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp @@ -0,0 +1,1204 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRBaseCharacter.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRBaseCharacter) + +#include "VRPlayerController.h" +#include "NavigationSystem.h" +#include "GameFramework/Controller.h" +#include "Components/CapsuleComponent.h" +#include "ParentRelativeAttachmentComponent.h" +#include "GripMotionControllerComponent.h" +#include "IMotionController.h" +#include "VRRootComponent.h" +#include "VRPathFollowingComponent.h" +#include "Net/UnrealNetwork.h" +#include "XRMotionControllerBase.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +DEFINE_LOG_CATEGORY(LogBaseVRCharacter); + +FName AVRBaseCharacter::LeftMotionControllerComponentName(TEXT("Left Grip Motion Controller")); +FName AVRBaseCharacter::RightMotionControllerComponentName(TEXT("Right Grip Motion Controller")); +FName AVRBaseCharacter::ReplicatedCameraComponentName(TEXT("VR Replicated Camera")); +FName AVRBaseCharacter::ParentRelativeAttachmentComponentName(TEXT("Parent Relative Attachment")); +FName AVRBaseCharacter::SmoothingSceneParentComponentName(TEXT("NetSmoother")); +FName AVRBaseCharacter::VRProxyComponentName(TEXT("VRProxy")); + + +FRepMovementVRCharacter::FRepMovementVRCharacter() +: Super() +{ + bJustTeleported = false; + bJustTeleportedGrips = false; + bPausedTracking = false; + PausedTrackingLoc = FVector::ZeroVector; + PausedTrackingRot = 0.f; + Owner = nullptr; +} + +AVRBaseCharacter::AVRBaseCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer/*.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName)*/.SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) + +{ + + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + + // Remove the movement jitter with slow speeds + MovementRep.LocationQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + + if (UCapsuleComponent * cap = GetCapsuleComponent()) + { + cap->SetCapsuleSize(16.0f, 96.0f); + cap->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + cap->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); + } + + NetSmoother = CreateDefaultSubobject(AVRBaseCharacter::SmoothingSceneParentComponentName); + if (NetSmoother) + { + NetSmoother->SetupAttachment(RootComponent); + + if (!bRetainRoomscale) + { + if (UVRRootComponent* MyRoot = Cast(RootComponent)) + { + NetSmoother->SetRelativeLocation(MyRoot->GetTargetHeightOffset()); + //VRProxyComponent->SetRelativeLocation(MyRoot->GetTargetHeightOffset()); + } + } + } + + VRProxyComponent = CreateDefaultSubobject(AVRBaseCharacter::VRProxyComponentName); + if (NetSmoother && VRProxyComponent) + { + VRProxyComponent->SetupAttachment(NetSmoother); + } + + VRReplicatedCamera = CreateDefaultSubobject(AVRBaseCharacter::ReplicatedCameraComponentName); + if (VRReplicatedCamera) + { + //VRReplicatedCamera->bOffsetByHMD = false; + VRReplicatedCamera->SetupAttachment(VRProxyComponent); + VRReplicatedCamera->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformCamera; + } + + VRMovementReference = NULL; + if (GetMovementComponent()) + { + VRMovementReference = Cast(GetMovementComponent()); + //AddTickPrerequisiteComponent(this->GetCharacterMovement()); + } + + ParentRelativeAttachment = CreateDefaultSubobject(AVRBaseCharacter::ParentRelativeAttachmentComponentName); + if (ParentRelativeAttachment && VRReplicatedCamera) + { + // Moved this to be root relative as the camera late updates were killing how it worked + ParentRelativeAttachment->SetupAttachment(VRProxyComponent); + //ParentRelativeAttachment->bOffsetByHMD = false; + ParentRelativeAttachment->AddTickPrerequisiteComponent(VRReplicatedCamera); + + if (USkeletalMeshComponent * SKMesh = GetMesh()) + { + SKMesh->SetupAttachment(ParentRelativeAttachment); + } + } + + LeftMotionController = CreateDefaultSubobject(AVRBaseCharacter::LeftMotionControllerComponentName); + if (IsValid(LeftMotionController)) + { + LeftMotionController->SetupAttachment(VRProxyComponent); + //LeftMotionController->MotionSource = FXRMotionControllerBase::LeftHandSourceId; + LeftMotionController->SetTrackingMotionSource(IMotionController::LeftHandSourceId); + //LeftMotionController->Hand = EControllerHand::Left; + //LeftMotionController->bOffsetByHMD = false; + //LeftMotionController->bUpdateInCharacterMovement = true; + // Keep the controllers ticking after movement + LeftMotionController->AddTickPrerequisiteComponent(GetCharacterMovement()); + LeftMotionController->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformLeftController; + } + + RightMotionController = CreateDefaultSubobject(AVRBaseCharacter::RightMotionControllerComponentName); + if (IsValid(RightMotionController)) + { + RightMotionController->SetupAttachment(VRProxyComponent); + //RightMotionController->MotionSource = FXRMotionControllerBase::RightHandSourceId; + RightMotionController->SetTrackingMotionSource(IMotionController::RightHandSourceId); + //RightMotionController->Hand = EControllerHand::Right; + //RightMotionController->bOffsetByHMD = false; + //RightMotionController->bUpdateInCharacterMovement = true; + // Keep the controllers ticking after movement + RightMotionController->AddTickPrerequisiteComponent(GetCharacterMovement()); + RightMotionController->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformRightController; + } + + OffsetComponentToWorld = FTransform(FQuat(0.0f, 0.0f, 0.0f, 1.0f), FVector::ZeroVector, FVector(1.0f)); + + + // Setting a minimum of every frame for replication consideration (UT uses this value for characters and projectiles). + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 100.0f; + + // This is for smooth turning, we have more of a use for this than FPS characters do + // Due to roll/pitch almost never being off 0 for VR the cost is just one byte so i'm fine defaulting it here + // End users can reset to byte components if they ever want too. + MovementRep.RotationQuantizationLevel = ERotatorQuantization::ShortComponents; + + VRReplicateCapsuleHeight = false; + + bUseExperimentalUnseatModeFix = true; + + ReplicatedMovementVR.Owner = this; + bFlagTeleported = false; + bTrackingPaused = false; + PausedTrackingLoc = FVector::ZeroVector; + PausedTrackingRot = 0.f; +} + + void AVRBaseCharacter::PossessedBy(AController* NewController) + { + Super::PossessedBy(NewController); + OwningVRPlayerController = Cast(Controller); + } + +void AVRBaseCharacter::OnRep_Controller() +{ + Super::OnRep_Controller(); + OwningVRPlayerController = Cast(Controller); +} + +void AVRBaseCharacter::OnRep_PlayerState() +{ + OnPlayerStateReplicated_Bind.Broadcast(GetPlayerState()); + Super::OnRep_PlayerState(); +} + +void AVRBaseCharacter::PostInitializeComponents() +{ + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_PostInitComponents); + + Super::PostInitializeComponents(); + + if (IsValid(this)) + { + if (NetSmoother) + { + CacheInitialMeshOffset(NetSmoother->GetRelativeLocation(), NetSmoother->GetRelativeRotation()); + } + + if (USkeletalMeshComponent * myMesh = GetMesh()) + { + // force animation tick after movement component updates + if (myMesh->PrimaryComponentTick.bCanEverTick && GetMovementComponent()) + { + myMesh->PrimaryComponentTick.AddPrerequisite(GetMovementComponent(), GetMovementComponent()->PrimaryComponentTick); + } + } + + if (GetCharacterMovement() && GetCapsuleComponent()) + { + GetCharacterMovement()->UpdateNavAgent(*GetCapsuleComponent()); + } + + if (Controller == nullptr && GetNetMode() != NM_Client) + { + if (GetCharacterMovement() && GetCharacterMovement()->bRunPhysicsWithNoController) + { + GetCharacterMovement()->SetDefaultMovementMode(); + } + } + } +} + +/*void AVRBaseCharacter::CacheInitialMeshOffset(FVector MeshRelativeLocation, FRotator MeshRelativeRotation) +{ + BaseTranslationOffset = MeshRelativeLocation; + BaseRotationOffset = MeshRelativeRotation.Quaternion(); + +#if ENABLE_NAN_DIAGNOSTIC + if (BaseRotationOffset.ContainsNaN()) + { + logOrEnsureNanError(TEXT("ACharacter::PostInitializeComponents detected NaN in BaseRotationOffset! (%s)"), *BaseRotationOffset.ToString()); + } + + if (GetMesh()) + { + const FRotator LocalRotation = GetMesh()->GetRelativeRotation(); + if (LocalRotation.ContainsNaN()) + { + logOrEnsureNanError(TEXT("ACharacter::PostInitializeComponents detected NaN in Mesh->RelativeRotation! (%s)"), *LocalRotation.ToString()); + } + } +#endif +}*/ + +void AVRBaseCharacter::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, SeatInformation, COND_None); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, VRReplicateCapsuleHeight, COND_None); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, ReplicatedCapsuleHeight, COND_SimulatedOnly); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, ReplicatedMovement); + + DOREPLIFETIME_CONDITION_NOTIFY(AVRBaseCharacter, ReplicatedMovementVR, COND_SimulatedOrPhysics, REPNOTIFY_Always); +} + +void AVRBaseCharacter::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AVRBaseCharacter, ReplicatedCapsuleHeight, VRReplicateCapsuleHeight); + DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AVRBaseCharacter, ReplicatedMovementVR, IsReplicatingMovement()); +} + +/*USkeletalMeshComponent* AVRBaseCharacter::GetIKMesh_Implementation() const +{ + return GetMesh(); +// return nullptr; +}*/ + +bool AVRBaseCharacter::Server_SetSeatedMode_Validate(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + return true; +} + +void AVRBaseCharacter::Server_SetSeatedMode_Implementation(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + SetSeatedMode(SeatParent, bSetSeatedMode, TargetTransform, InitialRelCameraTransform, AllowedRadius, AllowedRadiusThreshold, bZeroToHead, PostSeatedMovementMode); +} + +void AVRBaseCharacter::Server_ReZeroSeating_Implementation(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead) +{ + SeatInformation.StoredTargetTransform = NewTargetTransform; + SeatInformation.InitialRelCameraTransform = NewInitialRelCameraTransform; + + // Purify the yaw of the initial rotation + SeatInformation.InitialRelCameraTransform.SetRotation(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(NewInitialRelCameraTransform.Rotator()).Quaternion()); + + // #TODO: Need to handle non 1 scaled values here eventually + if (bZeroToHead) + { + FVector newLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + SeatInformation.StoredTargetTransform.AddToTranslation(FVector(0, 0, -newLocation.Z)); + } + + OnRep_SeatedCharInfo(); +} + +bool AVRBaseCharacter::Server_ReZeroSeating_Validate(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead) +{ + return true; +} + +void AVRBaseCharacter::Server_SeatedSnapTurn_Implementation(float Yaw) +{ + if(VRMovementReference && SeatInformation.bSitting) + { + FVRMoveActionContainer MoveActionTmp; + MoveActionTmp.MoveAction = EVRMoveAction::VRMOVEACTION_SnapTurn; + MoveActionTmp.MoveActionRot.Yaw = Yaw; + MoveActionTmp.VelRetentionSetting = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None; + VRMovementReference->MoveActionArray.MoveActions.Add(MoveActionTmp); + VRMovementReference->CheckForMoveAction(); + VRMovementReference->MoveActionArray.Clear(); + } +} + +bool AVRBaseCharacter::Server_SeatedSnapTurn_Validate(float Yaw) +{ + return true; +} + +void AVRBaseCharacter::OnCustomMoveActionPerformed_Implementation(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags) +{ + +} + +void AVRBaseCharacter::OnBeginWallPushback_Implementation(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput) +{ + +} + +void AVRBaseCharacter::OnEndWallPushback_Implementation() +{ + +} + +void AVRBaseCharacter::OnClimbingSteppedUp_Implementation() +{ + +} + +void AVRBaseCharacter::Server_SendTransformCamera_Implementation(FBPVRComponentPosRep NewTransform) +{ + if(VRReplicatedCamera) + VRReplicatedCamera->Server_SendCameraTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformCamera_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void AVRBaseCharacter::Server_SendTransformLeftController_Implementation(FBPVRComponentPosRep NewTransform) +{ + if (IsValid(LeftMotionController)) + LeftMotionController->Server_SendControllerTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformLeftController_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void AVRBaseCharacter::Server_SendTransformRightController_Implementation(FBPVRComponentPosRep NewTransform) +{ + if(IsValid(RightMotionController)) + RightMotionController->Server_SendControllerTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformRightController_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} +FVector AVRBaseCharacter::GetTeleportLocation(FVector OriginalLocation) +{ + return OriginalLocation; +} + + +void AVRBaseCharacter::NotifyOfTeleport(bool bRegisterAsTeleport) +{ + if (bRegisterAsTeleport) + { + if (GetNetMode() < ENetMode::NM_Client) + bFlagTeleported = true; + + if (VRMovementReference) + { + VRMovementReference->bNotifyTeleported = true; + } + } + + if (GetNetMode() < ENetMode::NM_Client) + { + if (bRegisterAsTeleport) + { + bFlagTeleported = true; + } + else + { + bFlagTeleportedGrips = true; + } + } + + if (IsValid(LeftMotionController)) + LeftMotionController->bIsPostTeleport = true; + + if (IsValid(RightMotionController)) + RightMotionController->bIsPostTeleport = true; +} + +void AVRBaseCharacter::OnRep_ReplicatedMovement() +{ + FRepMovement& ReppedMovement = GetReplicatedMovement_Mutable(); + + ReppedMovement.AngularVelocity = ReplicatedMovementVR.AngularVelocity; + ReppedMovement.bRepPhysics = ReplicatedMovementVR.bRepPhysics; + ReppedMovement.bSimulatedPhysicSleep = ReplicatedMovementVR.bSimulatedPhysicSleep; + ReppedMovement.LinearVelocity = ReplicatedMovementVR.LinearVelocity; + ReppedMovement.Location = ReplicatedMovementVR.Location; + ReppedMovement.Rotation = ReplicatedMovementVR.Rotation; + + Super::OnRep_ReplicatedMovement(); + + if (!IsLocallyControlled()) + { + if (ReplicatedMovementVR.bJustTeleported) + { + // Server should never get this value so it shouldn't be double throwing for them + NotifyOfTeleport(); + } + else if (ReplicatedMovementVR.bJustTeleportedGrips) + { + NotifyOfTeleport(false); + } + + bTrackingPaused = ReplicatedMovementVR.bPausedTracking; + if (bTrackingPaused) + { + PausedTrackingLoc = ReplicatedMovementVR.PausedTrackingLoc; + PausedTrackingRot = ReplicatedMovementVR.PausedTrackingRot; + } + } +} + +void AVRBaseCharacter::GatherCurrentMovement() +{ + Super::GatherCurrentMovement(); + + FRepMovement ReppedMovement = this->GetReplicatedMovement(); + + ReplicatedMovementVR.AngularVelocity = ReppedMovement.AngularVelocity; + ReplicatedMovementVR.bRepPhysics = ReppedMovement.bRepPhysics; + ReplicatedMovementVR.bSimulatedPhysicSleep = ReppedMovement.bSimulatedPhysicSleep; + ReplicatedMovementVR.LinearVelocity = ReppedMovement.LinearVelocity; + ReplicatedMovementVR.Location = ReppedMovement.Location; + ReplicatedMovementVR.Rotation = ReppedMovement.Rotation; + ReplicatedMovementVR.bJustTeleported = bFlagTeleported; + ReplicatedMovementVR.bJustTeleportedGrips = bFlagTeleportedGrips; + bFlagTeleported = false; + bFlagTeleportedGrips = false; + ReplicatedMovementVR.bPausedTracking = bTrackingPaused; + ReplicatedMovementVR.PausedTrackingLoc = PausedTrackingLoc; + ReplicatedMovementVR.PausedTrackingRot = PausedTrackingRot; + +} + + +void AVRBaseCharacter::OnRep_SeatedCharInfo() +{ + // Handle setting up the player here + + if (UPrimitiveComponent * root = Cast(GetRootComponent())) + { + if (SeatInformation.bSitting /*&& !SeatInformation.bWasSeated*/) // Removing WasSeated check because we may be switching seats + { + if (SeatInformation.bWasSeated) + { + if (SeatInformation.SeatParent != this->GetRootComponent()->GetAttachParent()) + { + InitSeatedModeTransition(); + } + else // Is just a reposition + { + //if (this->Role != ROLE_SimulatedProxy) + ZeroToSeatInformation(); + } + + } + else + { + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast(GetMovementComponent())) + { + charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + }*/ + } + else + { + if (VRMovementReference) + { + VRMovementReference->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + } + } + } + } + else if (!SeatInformation.bSitting && SeatInformation.bWasSeated) + { + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast(GetMovementComponent())) + { + charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->SetComponentTickEnabled(true); + }*/ + + } + else + { + if (VRMovementReference) + { + VRMovementReference->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + } + } + } + } +} + +void AVRBaseCharacter::InitSeatedModeTransition() +{ + if (UPrimitiveComponent * root = Cast(GetRootComponent())) + { + if (SeatInformation.bSitting /*&& !SeatInformation.bWasSeated*/) // Removing WasSeated check because we may be switching seats + { + + if (SeatInformation.SeatParent /*&& !root->IsAttachedTo(SeatInformation.SeatParent)*/) + { + FAttachmentTransformRules TransformRule = FAttachmentTransformRules::SnapToTargetNotIncludingScale; + TransformRule.bWeldSimulatedBodies = true; + AttachToComponent(SeatInformation.SeatParent, TransformRule); + } + + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + if (VRMovementReference) + { + //charMovement->DisableMovement(); + //charMovement->SetComponentTickEnabled(false); + //charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + } + + root->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Set it before it is set below + if (!SeatInformation.bWasSeated) + SeatInformation.bOriginalControlRotation = bUseControllerRotationYaw; + + SeatInformation.bWasSeated = true; + bUseControllerRotationYaw = false; // This forces rotation in world space, something that we don't want + ZeroToSeatInformation(); + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + else + { + if (VRMovementReference) + { + //charMovement->DisableMovement(); + //charMovement->SetComponentTickEnabled(false); + //charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + //charMovement->bIgnoreClientMovementErrorChecksAndCorrection = true; + + if (this->GetLocalRole() == ROLE_AutonomousProxy) + { + FNetworkPredictionData_Client_Character* ClientData = VRMovementReference->GetPredictionData_Client_Character(); + check(ClientData); + + if (ClientData->SavedMoves.Num()) + { + // Ack our most recent move, we don't want to start sending old moves after un seating. + ClientData->AckMove(ClientData->SavedMoves.Num() - 1, *VRMovementReference); + } + } + + } + + root->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Set it before it is set below + if (!SeatInformation.bWasSeated) + { + SeatInformation.bOriginalControlRotation = bUseControllerRotationYaw; + } + + SeatInformation.bWasSeated = true; + bUseControllerRotationYaw = false; // This forces rotation in world space, something that we don't want + + ZeroToSeatInformation(); + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + } + else if (!SeatInformation.bSitting && SeatInformation.bWasSeated) + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + root->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + + + bUseControllerRotationYaw = SeatInformation.bOriginalControlRotation; + + SetActorLocationAndRotationVR(SeatInformation.StoredTargetTransform.GetTranslation(), SeatInformation.StoredTargetTransform.Rotator(), true, true, true); + + if (IsValid(LeftMotionController)) + { + LeftMotionController->PostTeleportMoveGrippedObjects(); + } + + if (IsValid(RightMotionController)) + { + RightMotionController->PostTeleportMoveGrippedObjects(); + } + + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast(GetMovementComponent())) + { + charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->SetComponentTickEnabled(true); + }*/ + + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + else + { + if (VRMovementReference) + { + //charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->bIgnoreClientMovementErrorChecksAndCorrection = false; + //charMovement->SetComponentTickEnabled(true); + + if (this->GetLocalRole() == ROLE_Authority) + { + if (bUseExperimentalUnseatModeFix) + { + VRMovementReference->bJustUnseated = true; + FNetworkPredictionData_Server_Character * ServerData = VRMovementReference->GetPredictionData_Server_Character(); + check(ServerData); + ServerData->CurrentClientTimeStamp = 0.0f; + ServerData->PendingAdjustment = FClientAdjustment(); + //ServerData->CurrentClientTimeStamp = 0.f; + //ServerData->ServerAccumulatedClientTimeStamp = 0.0f; + //ServerData->LastUpdateTime = 0.f; + ServerData->ServerTimeStampLastServerMove = 0.f; + ServerData->bForceClientUpdate = false; + ServerData->TimeDiscrepancy = 0.f; + ServerData->bResolvingTimeDiscrepancy = false; + ServerData->TimeDiscrepancyResolutionMoveDeltaOverride = 0.f; + ServerData->TimeDiscrepancyAccumulatedClientDeltasSinceLastServerTick = 0.f; + } + //charMovement->ForceReplicationUpdate(); + //FNetworkPredictionData_Server_Character * ServerData = charMovement->GetPredictionData_Server_Character(); + //check(ServerData); + + // Reset client timestamp check so that there isn't a delay on ending seated mode before we accept movement packets + //ServerData->CurrentClientTimeStamp = 1.f; + } + } + + bUseControllerRotationYaw = SeatInformation.bOriginalControlRotation; + + // Re-purposing them for the new location and rotations + SetActorLocationAndRotationVR(SeatInformation.StoredTargetTransform.GetTranslation(), SeatInformation.StoredTargetTransform.Rotator(), true, true, true); + LeftMotionController->PostTeleportMoveGrippedObjects(); + RightMotionController->PostTeleportMoveGrippedObjects(); + + // Enable collision now + root->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + SeatInformation.ClearTempVals(); + } + } + } +} + +void AVRBaseCharacter::TickSeatInformation(float DeltaTime) +{ + float LastThresholdScaler = SeatInformation.CurrentThresholdScaler; + bool bLastOverThreshold = SeatInformation.bIsOverThreshold; + + FVector NewLoc = VRReplicatedCamera->ReplicatedCameraTransform.Position;//GetRelativeLocation(); + FVector OrigLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + + if (!SeatInformation.bZeroToHead) + { + NewLoc.Z = 0.0f; + OrigLocation.Z = 0.0f; + } + + if (FMath::IsNearlyZero(SeatInformation.AllowedRadius)) + { + // Nothing to process here, seated mode isn't sticking to a set radius + if (SeatInformation.bIsOverThreshold) + { + SeatInformation.bIsOverThreshold = false; + bLastOverThreshold = false; + } + return; + } + + float AbsDistance = FMath::Abs(FVector::Dist(OrigLocation, NewLoc)); + + //FTransform newTrans = SeatInformation.StoredTargetTransform * SeatInformation.SeatParent->GetComponentTransform(); + + // If over the allowed distance + if (AbsDistance > SeatInformation.AllowedRadius) + { + // Force them back into range + FVector diff = NewLoc - OrigLocation; + diff.Normalize(); + + if (bRetainRoomscale) + { + diff = (-diff * (AbsDistance - SeatInformation.AllowedRadius)); + SetSeatRelativeLocationAndRotationVR(diff); + } + else + { + diff = (diff * (SeatInformation.AllowedRadius)); + diff.Z = 0.0f; + SetSeatRelativeLocationAndRotationVR(diff); + } + + SeatInformation.bWasOverLimit = true; + } + else if (SeatInformation.bWasOverLimit) // Make sure we are in the zero point otherwise + { + if (bRetainRoomscale) + { + SetSeatRelativeLocationAndRotationVR(FVector::ZeroVector); + } + else + { + FVector diff = NewLoc - OrigLocation; + diff.Z = 0.0f; + SetSeatRelativeLocationAndRotationVR(diff); + } + + SeatInformation.bWasOverLimit = false; + } + else + { + if (!bRetainRoomscale) + { + FVector diff = NewLoc - OrigLocation; + diff.Z = 0.0f; + SetSeatRelativeLocationAndRotationVR(diff); + } + } + + if (AbsDistance > SeatInformation.AllowedRadius - SeatInformation.AllowedRadiusThreshold) + SeatInformation.bIsOverThreshold = true; + else + SeatInformation.bIsOverThreshold = false; + + SeatInformation.CurrentThresholdScaler = FMath::Clamp((AbsDistance - (SeatInformation.AllowedRadius - SeatInformation.AllowedRadiusThreshold)) / SeatInformation.AllowedRadiusThreshold, 0.0f, 1.0f); + + if (bLastOverThreshold != SeatInformation.bIsOverThreshold || !FMath::IsNearlyEqual(LastThresholdScaler, SeatInformation.CurrentThresholdScaler)) + { + OnSeatThreshholdChanged(!SeatInformation.bIsOverThreshold, SeatInformation.CurrentThresholdScaler); + OnSeatThreshholdChanged_Bind.Broadcast(!SeatInformation.bIsOverThreshold, SeatInformation.CurrentThresholdScaler); + } +} + +bool AVRBaseCharacter::SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform TargetTransform, FTransform InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + if (!this->HasAuthority()) + return false; + + if (bSetSeatedMode) + { + if (!SeatParent) + return false; + + // Automate the intial relative camera transform for this mode + // I think we can remove the initial value alltogether eventually right? + if (!bRetainRoomscale) + { + InitialRelCameraTransform = FTransform(VRReplicatedCamera->ReplicatedCameraTransform.Rotation, VRReplicatedCamera->ReplicatedCameraTransform.Position, VRReplicatedCamera->GetComponentScale()); + } + + SeatInformation.SeatParent = SeatParent; + SeatInformation.bSitting = true; + SeatInformation.bZeroToHead = bZeroToHead; + SeatInformation.StoredTargetTransform = TargetTransform; + SeatInformation.InitialRelCameraTransform = InitialRelCameraTransform; + + // Purify the yaw of the initial rotation + SeatInformation.InitialRelCameraTransform.SetRotation(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(InitialRelCameraTransform.Rotator()).Quaternion()); + SeatInformation.AllowedRadius = AllowedRadius; + SeatInformation.AllowedRadiusThreshold = AllowedRadiusThreshold; + + // #TODO: Need to handle non 1 scaled values here eventually + if (bZeroToHead) + { + FVector newLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + SeatInformation.StoredTargetTransform.AddToTranslation(FVector(0, 0, -newLocation.Z)); + } + + //SetReplicateMovement(false);/ / No longer doing this, allowing it to replicate down to simulated clients now instead + } + else + { + SeatInformation.SeatParent = nullptr; + SeatInformation.StoredTargetTransform = TargetTransform; + SeatInformation.PostSeatedMovementMode = PostSeatedMovementMode; + //SetReplicateMovement(true); // No longer doing this, allowing it to replicate down to simulated clients now instead + SeatInformation.bSitting = false; + } + + OnRep_SeatedCharInfo(); // Call this on server side because it won't call itself + NotifyOfTeleport(); // Teleport the controllers + + return true; +} + +void AVRBaseCharacter::SetSeatRelativeLocationAndRotationVR(FVector DeltaLoc) +{ + /*if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + NewLoc = NewLoc + Pivot; + NewLoc -= NewRot.RotateVector(Pivot); + + SetActorRelativeTransform(FTransform(NewRot, NewLoc, GetCapsuleComponent()->RelativeScale3D));*/ + + FVector ZOffset = -GetTargetHeightOffset() * GetCapsuleComponent()->GetRelativeScale3D(); + + FTransform NewTrans = SeatInformation.StoredTargetTransform;// *SeatInformation.SeatParent->GetComponentTransform(); + + if (!bRetainRoomscale) + { + NewTrans.SetTranslation(FVector(0.0f, 0.0f, NewTrans.GetTranslation().Z)); + } + + + FVector NewLocation; + FRotator NewRotation; + FVector PivotPoint = bRetainRoomscale ? SeatInformation.InitialRelCameraTransform.GetTranslation() : FVector::ZeroVector; + PivotPoint.Z = 0.0f; + + NewRotation = SeatInformation.InitialRelCameraTransform.Rotator(); + NewRotation = (NewRotation.Quaternion().Inverse() * NewTrans.GetRotation()).Rotator(); + NewLocation = NewTrans.GetTranslation(); + NewLocation -= NewRotation.RotateVector(PivotPoint + (-DeltaLoc)); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorRelativeTransform(FTransform(NewRotation, NewLocation + ZOffset, GetCapsuleComponent()->GetRelativeScale3D())); +} + +FVector AVRBaseCharacter::GetProjectedVRLocation() const +{ + return GetVRLocation_Inline(); +} + +FVector AVRBaseCharacter::AddActorWorldRotationVR(FRotator DeltaRot, bool bUseYawOnly, bool bRotateAroundCapsule) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector OrigLocation = GetActorLocation(); + FVector PivotPoint = GetActorTransform().InverseTransformPosition(bRotateAroundCapsule ? GetVRLocation_Inline() : GetProjectedVRLocation()); + PivotPoint.Z = 0.0f; + + NewRotation = bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + + if (bUseYawOnly) + { + NewRotation.Pitch = 0.0f; + NewRotation.Roll = 0.0f; + } + + NewLocation = OrigLocation + NewRotation.RotateVector(PivotPoint); + NewRotation = (NewRotation.Quaternion() * DeltaRot.Quaternion()).Rotator(); + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation); + return NewLocation - OrigLocation; +} + +FVector AVRBaseCharacter::SetActorRotationVR(FRotator NewRot, bool bUseYawOnly, bool bAccountForHMDRotation, bool bRotateAroundCapsule) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector OrigLocation = GetActorLocation(); + FVector PivotPoint = GetActorTransform().InverseTransformPosition(bRotateAroundCapsule ? GetVRLocation_Inline() : GetProjectedVRLocation()); + PivotPoint.Z = 0.0f; + + FRotator OrigRotation = bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + + if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + if (bAccountForHMDRotation) + { + NewRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRReplicatedCamera->GetRelativeRotation()); + NewRotation = (NewRot.Quaternion() * NewRotation.Quaternion().Inverse()).Rotator(); + } + else + NewRotation = NewRot; + + NewLocation = OrigLocation + OrigRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation); + return NewLocation - OrigLocation; +} + +FVector AVRBaseCharacter::SetActorLocationAndRotationVR(FVector NewLoc, FRotator NewRot, bool bUseYawOnly, bool bAccountForHMDRotation, bool bTeleport, bool bRotateAroundCapsule) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector PivotPoint = GetActorTransform().InverseTransformPosition(bRotateAroundCapsule ? GetVRLocation_Inline() : GetProjectedVRLocation()); + PivotPoint.Z = 0.0f; + + if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + if (bAccountForHMDRotation) + { + NewRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRReplicatedCamera->GetRelativeRotation());//bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + NewRotation = (NewRot.Quaternion() * NewRotation.Quaternion().Inverse()).Rotator(); + } + else + NewRotation = NewRot; + + NewLocation = NewLoc;// +PivotPoint;// NewRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation, false, nullptr, bTeleport ? ETeleportType::TeleportPhysics : ETeleportType::None); + return NewLocation - NewLoc; +} + +FVector AVRBaseCharacter::SetActorLocationVR(FVector NewLoc, bool bTeleport, bool bSetCapsuleLocation) +{ + FVector NewLocation; + FRotator NewRotation; + FVector PivotOffsetVal = (bSetCapsuleLocation ? GetVRLocation_Inline() : GetProjectedVRLocation()) - GetActorLocation(); + PivotOffsetVal.Z = 0.0f; + + + NewLocation = NewLoc - PivotOffsetVal;// +PivotPoint;// NewRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocation(NewLocation, false, nullptr, bTeleport ? ETeleportType::TeleportPhysics : ETeleportType::None); + return NewLocation - NewLoc; +} + +void AVRBaseCharacter::OnRep_CapsuleHeight() +{ + if (!VRReplicateCapsuleHeight) + return; + + if (UCapsuleComponent* Capsule = Cast(GetRootComponent())) + { + if (ReplicatedCapsuleHeight.CapsuleHeight > 0.0f && !FMath::IsNearlyEqual(ReplicatedCapsuleHeight.CapsuleHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + { + SetCharacterHalfHeightVR(ReplicatedCapsuleHeight.CapsuleHeight, false); + } + } +} + +void AVRBaseCharacter::SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + if (UCapsuleComponent * Capsule = Cast(this->RootComponent)) + { + if (!FMath::IsNearlyEqual(NewRadius, Capsule->GetUnscaledCapsuleRadius()) || !FMath::IsNearlyEqual(NewHalfHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + Capsule->SetCapsuleSize(NewRadius, NewHalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client && VRReplicateCapsuleHeight) + ReplicatedCapsuleHeight.CapsuleHeight = Capsule->GetUnscaledCapsuleHalfHeight(); + } +} + +void AVRBaseCharacter::SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + if (UCapsuleComponent * Capsule = Cast(this->RootComponent)) + { + if (!FMath::IsNearlyEqual(HalfHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + Capsule->SetCapsuleHalfHeight(HalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client && VRReplicateCapsuleHeight) + ReplicatedCapsuleHeight.CapsuleHeight = Capsule->GetUnscaledCapsuleHalfHeight(); + } +} + +void AVRBaseCharacter::ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf FilterClass, bool bAllowPartialPaths) +{ + UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent(Controller->GetWorld()) : nullptr; + if (NavSys == nullptr || Controller == nullptr ) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("UVRSimpleCharacter::ExtendedSimpleMoveToLocation called for NavSys:%s Controller:%s (if any of these is None then there's your problem"), + *GetNameSafe(NavSys), *GetNameSafe(Controller)); + return; + } + + UPathFollowingComponent* PFollowComp = nullptr; + //Controller->InitNavigationControl(PFollowComp); + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass(); + if (PFollowComp == nullptr) + { + PFollowComp = NewObject(Controller); + PFollowComp->RegisterComponentWithWorld(Controller->GetWorld()); + PFollowComp->Initialize(); + } + } + + if (PFollowComp == nullptr) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - No PathFollowingComponent Found")); + return; + } + + if (!PFollowComp->IsPathFollowingAllowed()) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - Path Following Movement Is Not Set To Allowed")); + return; + } + + EPathFollowingReachMode ReachMode; + if (bStopOnOverlap) + ReachMode = EPathFollowingReachMode::OverlapAgent; + else + ReachMode = EPathFollowingReachMode::ExactLocation; + + bool bAlreadyAtGoal = false; + + if(UVRPathFollowingComponent * pathcomp = Cast(PFollowComp)) + bAlreadyAtGoal = pathcomp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + else + bAlreadyAtGoal = PFollowComp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + + // script source, keep only one move request at time + if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + if (GetNetMode() == ENetMode::NM_Client) + { + // Stop the movement here, not keeping the velocity because it bugs out for clients, might be able to fix. + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, /*bAlreadyAtGoal ? */EPathFollowingVelocityMode::Reset /*: EPathFollowingVelocityMode::Keep*/); + } + else + { + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep); + } + } + + if (bAlreadyAtGoal) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Success); + } + else + { + const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef()); + if (NavData) + { + FPathFindingQuery Query(Controller, *NavData, Controller->GetNavAgentLocation(), GoalLocation); + FPathFindingResult Result = NavSys->FindPathSync(Query); + if (Result.IsSuccessful()) + { + FAIMoveRequest MoveReq(GoalLocation); + MoveReq.SetUsePathfinding(bUsePathfinding); + MoveReq.SetAllowPartialPath(bAllowPartialPaths); + MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation); + MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); + MoveReq.SetAcceptanceRadius(AcceptanceRadius); + MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); + MoveReq.SetCanStrafe(bCanStrafe); + MoveReq.SetReachTestIncludesGoalRadius(true); + + PFollowComp->RequestMove(/*FAIMoveRequest(GoalLocation)*/MoveReq, Result.Path); + } + else if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid); + } + } + } +} + +bool AVRBaseCharacter::GetCurrentNavigationPathPoints(TArray& NavigationPointList) +{ + UPathFollowingComponent* PFollowComp = nullptr; + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass(); + if (PFollowComp) + { + FNavPathSharedPtr NavPtr = PFollowComp->GetPath(); + if (NavPtr.IsValid()) + { + TArray& NavPoints = NavPtr->GetPathPoints(); + if (NavPoints.Num()) + { + FTransform BaseTransform = FTransform::Identity; + if (AActor* BaseActor = NavPtr->GetBaseActor()) + { + BaseTransform = BaseActor->GetActorTransform(); + } + + NavigationPointList.Empty(NavPoints.Num()); + NavigationPointList.AddUninitialized(NavPoints.Num()); + + int counter = 0; + for (FNavPathPoint& pt : NavPoints) + { + NavigationPointList[counter++] = BaseTransform.TransformPosition(pt.Location); + } + + return true; + } + } + + return false; + } + } + + return false; +} + +void AVRBaseCharacter::NavigationMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) +{ + this->Controller->StopMovement(); + ReceiveNavigationMoveCompleted(Result.Code); +} + +EPathFollowingStatus::Type AVRBaseCharacter::GetMoveStatus() const +{ + if (!Controller) + return EPathFollowingStatus::Idle; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass()) + { + pathComp->GetStatus(); + } + + return EPathFollowingStatus::Idle; +} + +bool AVRBaseCharacter::HasPartialPath() const +{ + if (!Controller) + return false; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass()) + { + return pathComp->HasPartialPath(); + } + + return false; +} + +void AVRBaseCharacter::StopNavigationMovement() +{ + if (!Controller) + return; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass()) + { + // @note FPathFollowingResultFlags::ForcedScript added to make AITask_MoveTo instances + // not ignore OnRequestFinished notify that's going to be sent out due to this call + pathComp->AbortMove(*this, FPathFollowingResultFlags::MovementStop | FPathFollowingResultFlags::ForcedScript); + } +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp new file mode 100644 index 0000000..e5869e6 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp @@ -0,0 +1,2310 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "VRBaseCharacterMovementComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRBaseCharacterMovementComponent) + +#include "VRBPDatatypes.h" +#include "ParentRelativeAttachmentComponent.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "VRRootComponent.h" +#include "AITypes.h" +#include "GripMotionControllerComponent.h" +#include "AI/Navigation/NavigationTypes.h" +#include "Navigation/PathFollowingComponent.h" +#include "VRPlayerController.h" +#include "GameFramework/PhysicsVolume.h" + + +DEFINE_LOG_CATEGORY(LogVRBaseCharacterMovement); + +UVRBaseCharacterMovementComponent::UVRBaseCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + + //#TODO: Might not be ready to make this change globally yet.... + + // Set Acceleration and braking deceleration walking to high values to avoid ramp up on speed + // Realized that I wasn't doing this here for people to default to no acceleration. + /*this->bRequestedMoveUseAcceleration = false; + this->MaxAcceleration = 200048.0f; + this->BrakingDecelerationWalking = 200048.0f; + */ + + AdditionalVRInputVector = FVector::ZeroVector; + CustomVRInputVector = FVector::ZeroVector; + TrackingLossThreshold = 6000.f; + bApplyAdditionalVRInputVectorAsNegative = true; + bHadExtremeInput = false; + bHoldPositionOnTrackingLossThresholdHit = false; + + VRClimbingStepHeight = 96.0f; + VRClimbingEdgeRejectDistance = 5.0f; + VRClimbingStepUpMultiplier = 1.0f; + bClampClimbingStepUp = false; + VRClimbingStepUpMaxSize = 20.0f; + + VRClimbingMaxReleaseVelocitySize = 800.0f; + SetDefaultPostClimbMovementOnStepUp = true; + DefaultPostClimbMovement = EVRConjoinedMovementModes::C_MOVE_Falling; + + bIgnoreSimulatingComponentsInFloorCheck = true; + + VRWallSlideScaler = 1.0f; + VRLowGravWallFrictionScaler = 1.0f; + VRLowGravIgnoresDefaultFluidFriction = true; + + VREdgeRejectDistance = 0.01f; // Rounded minimum of root movement + + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; + + NetworkSmoothingMode = ENetworkSmoothingMode::Exponential; + + bWasInPushBack = false; + bIsInPushBack = false; + + bRunControlRotationInMovementComponent = true; + + // Allow merging dual movements, generally this is wanted for the perf increase + bEnableServerDualMoveScopedMovementUpdates = true; + + bNotifyTeleported = false; + + bJustUnseated = false; + + bUseClientControlRotation = true; + bDisableSimulatedTickWhenSmoothingMovement = true; + bCapHMDMovementToMaxMovementSpeed = false; + + SetNetworkMoveDataContainer(VRNetworkMoveDataContainer); + SetMoveResponseDataContainer(VRMoveResponseDataContainer); +} + +// Rewind the relative movement that we had with the HMD +void UVRBaseCharacterMovementComponent::RewindVRRelativeMovement() +{ + if (bApplyAdditionalVRInputVectorAsNegative && (BaseVRCharacterOwner && BaseVRCharacterOwner->bRetainRoomscale)) + { + //FHitResult AHit; + MoveUpdatedComponent(-AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), false); + //SafeMoveUpdatedComponent(-AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), false, AHit); + } +} + +void UVRBaseCharacterMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) +{ + if (!HasValidData()) + { + return; + } + + // Clear out the old custom input vector, it will pollute the pool now that all modes allow it. + CustomVRInputVector = FVector::ZeroVector; + + if (PreviousMovementMode == EMovementMode::MOVE_Custom && PreviousCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (MovementMode != EMovementMode::MOVE_Custom || CustomMovementMode != (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (AVRBaseCharacter * BaseOwner = Cast(CharacterOwner)) + { + BaseOwner->InitSeatedModeTransition(); + } + } + } + + if (MovementMode == EMovementMode::MOVE_Custom) + { + if (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + // Kill velocity and clear queued up events + StopMovementKeepPathing(); + CharacterOwner->ResetJumpState(); + ClearAccumulatedForces(); + + if (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (AVRBaseCharacter * BaseOwner = Cast(CharacterOwner)) + { + BaseOwner->InitSeatedModeTransition(); + } + } + } + } + + Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode); +} + +bool UVRBaseCharacterMovementComponent::ForcePositionUpdate(float DeltaTime) +{ + // Skip force updating position if we are seated. + if ((MovementMode == EMovementMode::MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated)) + { + return false; + } + + return Super::ForcePositionUpdate(DeltaTime); +} + +bool UVRBaseCharacterMovementComponent::ClientUpdatePositionAfterServerUpdate() +{ + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementClientUpdatePositionAfterServerUpdate); + if (!HasValidData()) + { + return false; + } + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + check(ClientData); + + if (!ClientData->bUpdatePosition) + { + return false; + } + + ClientData->bUpdatePosition = false; + + // Don't do any network position updates on things running PHYS_RigidBody + if (CharacterOwner->GetRootComponent() && CharacterOwner->GetRootComponent()->IsSimulatingPhysics()) + { + return false; + } + + if (ClientData->SavedMoves.Num() == 0) + { + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientUpdatePositionAfterServerUpdate No saved moves to replay"), ClientData->SavedMoves.Num()); + + // With no saved moves to resimulate, the move the server updated us with is the last move we've done, no resimulation needed. + CharacterOwner->bClientResimulateRootMotion = false; + if (CharacterOwner->bClientResimulateRootMotionSources) + { + // With no resimulation, we just update our current root motion to what the server sent us + UE_LOG(LogRootMotion, VeryVerbose, TEXT("CurrentRootMotion getting updated to ServerUpdate state: %s"), *CharacterOwner->GetName()); + CurrentRootMotion.UpdateStateFrom(CharacterOwner->SavedRootMotion); + CharacterOwner->bClientResimulateRootMotionSources = false; + } + CharacterOwner->SavedRootMotion.Clear(); + + return false; + } + + // Save important values that might get affected by the replay. + const float SavedAnalogInputModifier = AnalogInputModifier; + const FRootMotionMovementParams BackupRootMotionParams = RootMotionParams; // For animation root motion + const FRootMotionSourceGroup BackupRootMotion = CurrentRootMotion; + const bool bRealPressedJump = CharacterOwner->bPressedJump; + const float RealJumpMaxHoldTime = CharacterOwner->JumpMaxHoldTime; + const int32 RealJumpMaxCount = CharacterOwner->JumpMaxCount; + const bool bRealCrouch = bWantsToCrouch; + const bool bRealForceMaxAccel = bForceMaxAccel; + CharacterOwner->bClientWasFalling = (MovementMode == MOVE_Falling); + CharacterOwner->bClientUpdating = true; + bForceNextFloorCheck = true; + + // Store out our custom properties to restore after replaying + const FVRMoveActionArray Orig_MoveActions = MoveActionArray; + const FVector Orig_CustomInput = CustomVRInputVector; + const EVRConjoinedMovementModes Orig_VRReplicatedMovementMode = VRReplicatedMovementMode; + const FVector Orig_RequestedVelocity = RequestedVelocity; + const bool Orig_HasRequestedVelocity = HasRequestedVelocity(); + + // Replay moves that have not yet been acked. + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientUpdatePositionAfterServerUpdate Replaying %d Moves, starting at Timestamp %f"), ClientData->SavedMoves.Num(), ClientData->SavedMoves[0]->TimeStamp); + for (int32 i = 0; i < ClientData->SavedMoves.Num(); i++) + { + FSavedMove_Character* const CurrentMove = ClientData->SavedMoves[i].Get(); + checkSlow(CurrentMove != nullptr); + + // Make current SavedMove accessible to any functions that might need it. + SetCurrentReplayedSavedMove(CurrentMove); + + CurrentMove->PrepMoveFor(CharacterOwner); + + if (ShouldUsePackedMovementRPCs()) + { + // Make current move data accessible to MoveAutonomous or any other functions that might need it. + if (FCharacterNetworkMoveData* NewMove = GetNetworkMoveDataContainer().GetNewMoveData()) + { + SetCurrentNetworkMoveData(NewMove); + NewMove->ClientFillNetworkMoveData(*CurrentMove, FCharacterNetworkMoveData::ENetworkMoveType::NewMove); + } + } + + MoveAutonomous(CurrentMove->TimeStamp, CurrentMove->DeltaTime, CurrentMove->GetCompressedFlags(), CurrentMove->Acceleration); + + CurrentMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Replay); + SetCurrentNetworkMoveData(nullptr); + SetCurrentReplayedSavedMove(nullptr); + } + const bool bPostReplayPressedJump = CharacterOwner->bPressedJump; + + if (FSavedMove_Character* const PendingMove = ClientData->PendingMove.Get()) + { + PendingMove->bForceNoCombine = true; + } + + // Restore saved values. + AnalogInputModifier = SavedAnalogInputModifier; + RootMotionParams = BackupRootMotionParams; + CurrentRootMotion = BackupRootMotion; + if (CharacterOwner->bClientResimulateRootMotionSources) + { + // If we were resimulating root motion sources, it's because we had mismatched state + // with the server - we just resimulated our SavedMoves and now need to restore + // CurrentRootMotion with the latest "good state" + UE_LOG(LogRootMotion, VeryVerbose, TEXT("CurrentRootMotion getting updated after ServerUpdate replays: %s"), *CharacterOwner->GetName()); + CurrentRootMotion.UpdateStateFrom(CharacterOwner->SavedRootMotion); + CharacterOwner->bClientResimulateRootMotionSources = false; + } + CharacterOwner->SavedRootMotion.Clear(); + CharacterOwner->bClientResimulateRootMotion = false; + CharacterOwner->bClientUpdating = false; + CharacterOwner->bPressedJump = bRealPressedJump || bPostReplayPressedJump; + CharacterOwner->JumpMaxHoldTime = RealJumpMaxHoldTime; + CharacterOwner->JumpMaxCount = RealJumpMaxCount; + bWantsToCrouch = bRealCrouch; + bForceMaxAccel = bRealForceMaxAccel; + bForceNextFloorCheck = true; + + // Restore our move actions + MoveActionArray = Orig_MoveActions; + CustomVRInputVector = Orig_CustomInput; + VRReplicatedMovementMode = Orig_VRReplicatedMovementMode; + RequestedVelocity = Orig_RequestedVelocity; + SetHasRequestedVelocity(Orig_HasRequestedVelocity); + + return (ClientData->SavedMoves.Num() > 0); +} + +void UVRBaseCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + // Skip calling into BP if we aren't locally controlled + if (CharacterOwner->IsLocallyControlled() && GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Climbing) + { + // Allow the player to run updates on the climb logic for CustomVRInputVector + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->UpdateClimbingMovement(DeltaTime); + } + } + + // Scope all of the movements, including PRC + { + UParentRelativeAttachmentComponent* OuterScopePRC = nullptr; + if (BaseVRCharacterOwner && BaseVRCharacterOwner->ParentRelativeAttachment && BaseVRCharacterOwner->ParentRelativeAttachment->bUpdateInCharacterMovement) + { + OuterScopePRC = BaseVRCharacterOwner->ParentRelativeAttachment; + } + + FScopedMovementUpdate ScopedPRCMovementUpdate(OuterScopePRC, EScopedUpdate::DeferredUpdates); + + { + UReplicatedVRCameraComponent* OuterScopeCamera = nullptr; + if (BaseVRCharacterOwner && BaseVRCharacterOwner->VRReplicatedCamera) + { + OuterScopeCamera = BaseVRCharacterOwner->VRReplicatedCamera; + } + + FScopedMovementUpdate ScopedCameraMovementUpdate(OuterScopeCamera, EScopedUpdate::DeferredUpdates); + + // Scope in the character movements first + { + // Scope these, they nest with Outer references so it should work fine + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + + //#TODO 5.0: Handle this? + /*FVector InputVector = FVector::ZeroVector; + bool bUsingAsyncTick = (CharacterMovementCVars::AsyncCharacterMovement == 1) && IsAsyncCallbackRegistered(); + if (!bUsingAsyncTick) + { + // Do not consume input if simulating asynchronously, we will consume input when filling out async inputs. + InputVector = ConsumeInputVector(); + }*/ + + + const FVector InputVector = ConsumeInputVector(); + if (!HasValidData() || ShouldSkipUpdate(DeltaTime)) + { + return; + } + + // Skip the perform movement logic, run the re-seat logic instead - running base movement component tick instead + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // See if we fell out of the world. + const bool bIsSimulatingPhysics = UpdatedComponent->IsSimulatingPhysics(); + if (CharacterOwner->GetLocalRole() == ROLE_Authority && (!bCheatFlying || bIsSimulatingPhysics) && !CharacterOwner->CheckStillInWorld()) + { + return; + } + + // If we are the owning client or the server then run the re-basing + if (CharacterOwner->GetLocalRole() > ROLE_SimulatedProxy) + { + // Run offset logic here, the server will update simulated proxies with the movement replication + if (AVRBaseCharacter* BaseChar = Cast(CharacterOwner)) + { + BaseChar->TickSeatInformation(DeltaTime); + } + + // Handle move actions here - Should be scoped + CheckForMoveAction(); + MoveActionArray.Clear(); + + if (CharacterOwner && !CharacterOwner->IsLocallyControlled() && DeltaTime > 0.0f) + { + // If not playing root motion, tick animations after physics. We do this here to keep events, notifies, states and transitions in sync with client updates. + if (!CharacterOwner->bClientUpdating && !CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaTime); + // TODO: SaveBaseLocation() in case tick moves us? + + // Trigger Events right away, as we could be receiving multiple ServerMoves per frame. + CharacterOwner->GetMesh()->ConditionallyDispatchQueuedAnimEvents(); + } + } + + } + else + { + if (bNetworkUpdateReceived) + { + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + } + } + } + else + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + // This should be valid for both Simulated and owning clients as well as the server + // Better here than in perform movement + if (UVRRootComponent* VRRoot = Cast(CharacterOwner->GetCapsuleComponent())) + { + // If we didn't move the capsule, have it update itself here so the visual and physics representation is correct + // We do this specifically to avoid double calling into the render / physics threads. + if (!VRRoot->bCalledUpdateTransform) + VRRoot->OnUpdateTransform_Public(EUpdateTransformFlags::None, ETeleportType::None); + } + + // Make sure these are cleaned out for the next frame + AdditionalVRInputVector = FVector::ZeroVector; + CustomVRInputVector = FVector::ZeroVector; + } + + if (bRunControlRotationInMovementComponent && CharacterOwner->IsLocallyControlled()) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->VRReplicatedCamera && BaseVRCharacterOwner->VRReplicatedCamera->bUsePawnControlRotation) + { + const AController* OwningController = BaseVRCharacterOwner->GetController(); + if (OwningController) + { + const FRotator PawnViewRotation = BaseVRCharacterOwner->GetViewRotation(); + if (!PawnViewRotation.Equals(BaseVRCharacterOwner->VRReplicatedCamera->GetComponentRotation())) + { + BaseVRCharacterOwner->VRReplicatedCamera->SetWorldRotation(PawnViewRotation); + } + } + } + } + } + + // If some of our important components run inside the cmc updates then lets update them now + if (OuterScopeCamera) + { + OuterScopeCamera->UpdateTracking(DeltaTime); + } + + if (OuterScopePRC) + { + OuterScopePRC->UpdateTracking(DeltaTime); + } + } + } + + if (bNotifyTeleported) + { + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->OnCharacterTeleported_Bind.Broadcast(); + bNotifyTeleported = false; + } + } +} + +bool UVRBaseCharacterMovementComponent::VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character & ServerData) +{ + // Server is auth on seated mode and we want to ignore incoming pending movements after we have decided to set the client to seated mode + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + return false; + + return Super::VerifyClientTimeStamp(TimeStamp, ServerData); +} + +void UVRBaseCharacterMovementComponent::StartPushBackNotification(FHitResult HitResult) +{ + bIsInPushBack = true; + + if (bWasInPushBack) + return; + + bWasInPushBack = true; + + if (AVRBaseCharacter * OwningCharacter = Cast(GetCharacterOwner())) + { + OwningCharacter->OnBeginWallPushback(HitResult, !Acceleration.Equals(FVector::ZeroVector), AdditionalVRInputVector); + } +} + +void UVRBaseCharacterMovementComponent::EndPushBackNotification() +{ + if (bIsInPushBack || !bWasInPushBack) + return; + + bIsInPushBack = false; + bWasInPushBack = false; + + if (AVRBaseCharacter * OwningCharacter = Cast(GetCharacterOwner())) + { + OwningCharacter->OnEndWallPushback(); + } +} + +FVector UVRBaseCharacterMovementComponent::GetActorFeetLocationVR() const +{ + if (AVRBaseCharacter * BaseCharacter = Cast(GetCharacterOwner())) + { + return UpdatedComponent ? (BaseCharacter->OffsetComponentToWorld.GetLocation() - FVector(0, 0, UpdatedComponent->Bounds.BoxExtent.Z)) : FNavigationSystem::InvalidLocation; + } + else + { + return UpdatedComponent ? (UpdatedComponent->GetComponentLocation() - FVector(0, 0, UpdatedComponent->Bounds.BoxExtent.Z)) : FNavigationSystem::InvalidLocation; + } +} + +void UVRBaseCharacterMovementComponent::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) +{ + if (AVRBaseCharacter* vrOwner = Cast(GetCharacterOwner())) + { + vrOwner->NavigationMoveCompleted(RequestID, Result); + } +} + +bool UVRBaseCharacterMovementComponent::FloorSweepTest( + FHitResult& OutHit, + const FVector& Start, + const FVector& End, + ECollisionChannel TraceChannel, + const struct FCollisionShape& CollisionShape, + const struct FCollisionQueryParams& Params, + const struct FCollisionResponseParams& ResponseParam +) const +{ + bool bBlockingHit = false; + + // #TODO: Report to epic that their FloorSweepTest was not using the gravity rotation so it didn't work when the capsule was out of range + + if (!bUseFlatBaseForFloorChecks) + { + bBlockingHit = GetWorld()->SweepSingleByChannel(OutHit, Start, End, GetWorldToGravityTransform(), TraceChannel, CollisionShape, Params, ResponseParam); + } + else + { + // Test with a box that is enclosed by the capsule. + const float CapsuleRadius = CollisionShape.GetCapsuleRadius(); + const float CapsuleHeight = CollisionShape.GetCapsuleHalfHeight(); + const FCollisionShape BoxShape = FCollisionShape::MakeBox(FVector(CapsuleRadius * 0.707f, CapsuleRadius * 0.707f, CapsuleHeight)); + + // First test with the box rotated so the corners are along the major axes (ie rotated 45 degrees). + bBlockingHit = GetWorld()->SweepSingleByChannel(OutHit, Start, End, FQuat(RotateGravityToWorld(FVector(0.f, 0.f, -1.f)), UE_PI * 0.25f), TraceChannel, BoxShape, Params, ResponseParam); + + if (!bBlockingHit) + { + // Test again with the same box, not rotated. + OutHit.Reset(1.f, false); + bBlockingHit = GetWorld()->SweepSingleByChannel(OutHit, Start, End, GetWorldToGravityTransform(), TraceChannel, BoxShape, Params, ResponseParam); + } + } + + return bBlockingHit; +} + +void UVRBaseCharacterMovementComponent::ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius, const FHitResult* DownwardSweepResult) const +{ + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("[Role:%d] ComputeFloorDist: %s at location %s"), (int32)CharacterOwner->GetLocalRole(), *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString()); + OutFloorResult.Clear(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + bool bSkipSweep = false; + if (DownwardSweepResult != NULL && DownwardSweepResult->IsValidBlockingHit()) + { + // Only if the supplied sweep was vertical and downward. + const bool bIsDownward = RotateWorldToGravity(DownwardSweepResult->TraceStart - DownwardSweepResult->TraceEnd).Z > 0; + const bool bIsVertical = RotateWorldToGravity(DownwardSweepResult->TraceStart - DownwardSweepResult->TraceEnd).SizeSquared2D() <= UE_KINDA_SMALL_NUMBER; + if (bIsDownward && bIsVertical) + { + // Reject hits that are barely on the cusp of the radius of the capsule + if (IsWithinEdgeTolerance(DownwardSweepResult->Location, DownwardSweepResult->ImpactPoint, PawnRadius)) + { + // Don't try a redundant sweep, regardless of whether this sweep is usable. + bSkipSweep = true; + + const bool bIsWalkable = IsWalkable(*DownwardSweepResult); + const float FloorDist = RotateWorldToGravity(CapsuleLocation - DownwardSweepResult->Location).Z; + OutFloorResult.SetFromSweep(*DownwardSweepResult, FloorDist, bIsWalkable); + + if (bIsWalkable) + { + // Use the supplied downward sweep as the floor hit result. + return; + } + } + } + } + + // We require the sweep distance to be >= the line distance, otherwise the HitResult can't be interpreted as the sweep result. + if (SweepDistance < LineDistance) + { + ensure(SweepDistance >= LineDistance); + return; + } + + bool bBlockingHit = false; + FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(ComputeFloorDist), false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(QueryParams, ResponseParam); + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + + // Skip physics bodies for floor check if we are skipping simulated objects + if (bIgnoreSimulatingComponentsInFloorCheck) + ResponseParam.CollisionResponse.PhysicsBody = ECollisionResponse::ECR_Ignore; + + // Sweep test + if (!bSkipSweep && SweepDistance > 0.f && SweepRadius > 0.f) + { + // Use a shorter height to avoid sweeps giving weird results if we start on a surface. + // This also allows us to adjust out of penetrations. + const float ShrinkScale = 0.9f; + const float ShrinkScaleOverlap = 0.1f; + float ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScale); + float TraceDist = SweepDistance + ShrinkHeight; + FCollisionShape CapsuleShape = FCollisionShape::MakeCapsule(SweepRadius, PawnHalfHeight - ShrinkHeight); + + FHitResult Hit(1.f); + bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + RotateGravityToWorld(FVector(0.f, 0.f, -TraceDist)), CollisionChannel, CapsuleShape, QueryParams, ResponseParam); + + if (bBlockingHit) + { + // Reject hits adjacent to us, we only care about hits on the bottom portion of our capsule. + // Check 2D distance to impact point, reject if within a tolerance from radius. + if (Hit.bStartPenetrating || !IsWithinEdgeTolerance(CapsuleLocation, Hit.ImpactPoint, CapsuleShape.Capsule.Radius)) + { + // Use a capsule with a slightly smaller radius and shorter height to avoid the adjacent object. + // Capsule must not be nearly zero or the trace will fall back to a line trace from the start point and have the wrong length. + CapsuleShape.Capsule.Radius = FMath::Max(0.f, CapsuleShape.Capsule.Radius - SWEEP_EDGE_REJECT_DISTANCE - UE_KINDA_SMALL_NUMBER); + if (!CapsuleShape.IsNearlyZero()) + { + ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScaleOverlap); + TraceDist = SweepDistance + ShrinkHeight; + CapsuleShape.Capsule.HalfHeight = FMath::Max(PawnHalfHeight - ShrinkHeight, CapsuleShape.Capsule.Radius); + Hit.Reset(1.f, false); + + bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + RotateGravityToWorld(FVector(0.f, 0.f, -TraceDist)), CollisionChannel, CapsuleShape, QueryParams, ResponseParam); + } + } + + // Reduce hit distance by ShrinkHeight because we shrank the capsule for the trace. + // We allow negative distances here, because this allows us to pull out of penetrations. + const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius); + const float SweepResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight); + + OutFloorResult.SetFromSweep(Hit, SweepResult, false); + if (Hit.IsValidBlockingHit() && IsWalkable(Hit)) + { + if (SweepResult <= SweepDistance) + { + // Hit within test distance. + OutFloorResult.bWalkableFloor = true; + return; + } + } + } + } + + // Since we require a longer sweep than line trace, we don't want to run the line trace if the sweep missed everything. + // We do however want to try a line trace if the sweep was stuck in penetration. + if (!OutFloorResult.bBlockingHit && !OutFloorResult.HitResult.bStartPenetrating) + { + OutFloorResult.FloorDist = SweepDistance; + return; + } + + // Line trace + if (LineDistance > 0.f) + { + const float ShrinkHeight = PawnHalfHeight; + const FVector LineTraceStart = CapsuleLocation; + const float TraceDist = LineDistance + ShrinkHeight; + const FVector Down = RotateGravityToWorld(FVector(0.f, 0.f, -TraceDist)); + QueryParams.TraceTag = SCENE_QUERY_STAT_NAME_ONLY(FloorLineTrace); + + FHitResult Hit(1.f); + bBlockingHit = GetWorld()->LineTraceSingleByChannel(Hit, LineTraceStart, LineTraceStart + Down, CollisionChannel, QueryParams, ResponseParam); + + if (bBlockingHit) + { + if (Hit.Time > 0.f) + { + // Reduce hit distance by ShrinkHeight because we started the trace higher than the base. + // We allow negative distances here, because this allows us to pull out of penetrations. + const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius); + const float LineResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight); + + OutFloorResult.bBlockingHit = true; + if (LineResult <= LineDistance && IsWalkable(Hit)) + { + OutFloorResult.SetFromLineTrace(Hit, OutFloorResult.FloorDist, LineResult, true); + return; + } + } + } + } + + // No hits were acceptable. + OutFloorResult.bWalkableFloor = false; +} + + +float UVRBaseCharacterMovementComponent::SlideAlongSurface(const FVector& Delta, float Time, const FVector& InNormal, FHitResult& Hit, bool bHandleImpact) +{ + // Am running the CharacterMovementComponents calculations manually here now prior to scaling down the delta + + if (!Hit.bBlockingHit) + { + return 0.f; + } + + FVector Normal(RotateWorldToGravity(InNormal)); + if (IsMovingOnGround()) + { + // We don't want to be pushed up an unwalkable surface. + if (Normal.Z > 0.f) + { + if (!IsWalkable(Hit)) + { + Normal = Normal.GetSafeNormal2D(); + } + } + else if (Normal.Z < -UE_KINDA_SMALL_NUMBER) + { + // Don't push down into the floor when the impact is on the upper portion of the capsule. + if (CurrentFloor.FloorDist < MIN_FLOOR_DIST && CurrentFloor.bBlockingHit) + { + const FVector FloorNormal = RotateWorldToGravity(CurrentFloor.HitResult.Normal); + const bool bFloorOpposedToMovement = (RotateWorldToGravity(Delta) | FloorNormal) < 0.f && (FloorNormal.Z < 1.f - UE_DELTA); + + if (bFloorOpposedToMovement) + { + Normal = FloorNormal; + } + + Normal = Normal.GetSafeNormal2D(); + } + } + } + + + /*if ((Delta | InNormal) <= -0.2) + { + + }*/ + + StartPushBackNotification(Hit); + + // If the movement mode is one where sliding is an issue in VR, scale the delta by the custom scaler now + // that we have already validated the floor normal. + // Otherwise just pass in as normal, either way skip the parents implementation as we are doing it now. + if (IsMovingOnGround() || (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)) + return Super::Super::SlideAlongSurface(Delta * VRWallSlideScaler, Time, RotateGravityToWorld(Normal), Hit, bHandleImpact); + else + return Super::Super::SlideAlongSurface(Delta, Time, RotateGravityToWorld(Normal), Hit, bHandleImpact); +} + +/*void UVRBaseCharacterMovementComponent::SetCrouchedHalfHeight(float NewCrouchedHalfHeight) +{ + this->CrouchedHalfHeight = NewCrouchedHalfHeight; +}*/ + +void UVRBaseCharacterMovementComponent::AddCustomReplicatedMovement(FVector Movement) +{ + // if we are a client then lets round this to match what it will be after net Replication + if (GetNetMode() == NM_Client) + CustomVRInputVector += RoundDirectMovement(Movement); + else + CustomVRInputVector += Movement; // If not a client, don't bother to round this down. +} + +void UVRBaseCharacterMovementComponent::ClearCustomReplicatedMovement() +{ + CustomVRInputVector = FVector::ZeroVector; +} + +void UVRBaseCharacterMovementComponent::CheckServerAuthedMoveAction() +{ + // If we are calling this on the server on a non owned character, there is no reason to wait around, just do the action now + // If we ARE locally controlled, keep the action inline with the movement component to maintain consistency + if (GetNetMode() < NM_Client) + { + ACharacter* OwningChar = GetCharacterOwner(); + if (OwningChar && !OwningChar->IsLocallyControlled()) + { + CheckForMoveAction(); + MoveActionArray.Clear(); + } + } +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SetTrackingPaused(bool bNewTrackingPaused) +{ + StoreSetTrackingPaused(bNewTrackingPaused); +} + +void UVRBaseCharacterMovementComponent::StoreSetTrackingPaused(bool bNewTrackingPaused) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_PauseTracking; + MoveAction.MoveActionFlags = bNewTrackingPaused; + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SnapTurn(float DeltaYawAngle, EVRMoveActionVelocityRetention VelocityRetention, bool bFlagGripTeleport, bool bFlagCharacterTeleport, bool bRotateAroundCapsule ) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_SnapTurn; + + // Removed 2 decimal precision rounding in favor of matching the actual replicated short fidelity instead. + // MoveAction.MoveActionRot = FRotator(0.0f, FMath::RoundToFloat(((FRotator(0.f,DeltaYawAngle, 0.f).Quaternion() * UpdatedComponent->GetComponentQuat()).Rotator().Yaw) * 100.f) / 100.f, 0.0f); + + // Setting to the exact same fidelity as the replicated value ends up being, losing some precision + FRotator TargetRotation = (UpdatedComponent->GetComponentQuat() * FRotator(0.f, DeltaYawAngle, 0.f).Quaternion()).Rotator(); + TargetRotation.Yaw = FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort(TargetRotation.Yaw)); + TargetRotation.Pitch = FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort(TargetRotation.Pitch)); + TargetRotation.Roll = FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort(TargetRotation.Roll)); + MoveAction.MoveActionRot = TargetRotation; + //MoveAction.MoveActionRot = FRotator( 0.0f, FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort(DeltaYawAngle)), 0.0f); + //FRotator(0.0f, FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort((FRotator(0.f, DeltaYawAngle, 0.f).Quaternion() * UpdatedComponent->GetComponentQuat()).Rotator().Yaw)), 0.0f); + + if (bFlagCharacterTeleport) + MoveAction.MoveActionFlags = 0x02;// .MoveActionRot.Roll = 2.0f; + else if(bFlagGripTeleport) + MoveAction.MoveActionFlags = 0x01;//MoveActionRot.Roll = bFlagGripTeleport ? 1.0f : 0.0f; + + if (bRotateAroundCapsule) + { + MoveAction.MoveActionFlags |= 0x08; + } + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + //MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + MoveAction.MoveActionDeltaYaw = FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort(DeltaYawAngle)); + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SetRotation(float NewYaw, EVRMoveActionVelocityRetention VelocityRetention, bool bFlagGripTeleport, bool bFlagCharacterTeleport, bool bRotateAroundCapsule) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_SetRotation; + MoveAction.MoveActionRot = FRotator(0.0f, FMath::RoundToFloat(NewYaw * 100.f) / 100.f, 0.0f); + + if (bFlagCharacterTeleport) + MoveAction.MoveActionFlags = 0x02;// .MoveActionRot.Roll = 2.0f; + else if (bFlagGripTeleport) + MoveAction.MoveActionFlags = 0x01;//MoveActionRot.Roll = bFlagGripTeleport ? 1.0f : 0.0f; + + if (bRotateAroundCapsule) + { + MoveAction.MoveActionFlags |= 0x08; + } + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + float DeltaYawAngle = FMath::FindDeltaAngleDegrees(UpdatedComponent->GetComponentRotation().Yaw, NewYaw); + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_Teleport(FVector TeleportLocation, FRotator TeleportRotation, EVRMoveActionVelocityRetention VelocityRetention, bool bSkipEncroachmentCheck) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_Teleport; + MoveAction.MoveActionLoc = RoundDirectMovement(TeleportLocation); + MoveAction.MoveActionRot.Yaw = FMath::RoundToFloat(TeleportRotation.Yaw * 100.f) / 100.f; + MoveAction.MoveActionFlags |= (uint8)bSkipEncroachmentCheck;//.MoveActionRot.Roll = bSkipEncroachmentCheck ? 1.0f : 0.0f; + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + float DeltaYawAngle = FMath::FindDeltaAngleDegrees(UpdatedComponent->GetComponentRotation().Yaw, TeleportRotation.Yaw); + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_StopAllMovement() +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_StopAllMovement; + MoveActionArray.MoveActions.Add(MoveAction); + + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SetGravityDirection(FVector NewGravityDirection, bool bOrientToNewGravity) +{ + if (NewGravityDirection.IsNearlyZero()) + { + return; + } + + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_SetGravityDirection; + MoveAction.MoveActionVel = NewGravityDirection.GetSafeNormal(); + MoveAction.MoveActionFlags |= (uint8)bOrientToNewGravity; + MoveActionArray.MoveActions.Add(MoveAction); + + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_Custom(EVRMoveAction MoveActionToPerform, EVRMoveActionDataReq DataRequirementsForMoveAction, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = MoveActionToPerform; + + // Round the vector to 2 decimal precision + MoveAction.MoveActionLoc = RoundDirectMovement(MoveActionVector); + MoveAction.MoveActionRot = MoveActionRotator; + MoveAction.MoveActionDataReq = DataRequirementsForMoveAction; + MoveAction.MoveActionFlags = MoveActionFlags; + MoveActionArray.MoveActions.Add(MoveAction); + + CheckServerAuthedMoveAction(); +} + +bool UVRBaseCharacterMovementComponent::CheckForMoveAction() +{ + if (!BaseVRCharacterOwner) + return true; + + for (FVRMoveActionContainer& MoveAction : MoveActionArray.MoveActions) + { + switch (MoveAction.MoveAction) + { + case EVRMoveAction::VRMOVEACTION_SnapTurn: + { + /*return */DoMASnapTurn(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_Teleport: + { + if (!BaseVRCharacterOwner->SeatInformation.bSitting) + { + /*return */DoMATeleport(MoveAction); + } + }break; + case EVRMoveAction::VRMOVEACTION_StopAllMovement: + { + /*return */DoMAStopAllMovement(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_SetGravityDirection: + { + if (!BaseVRCharacterOwner->SeatInformation.bSitting) + { + /*return */DoMASetGravityDirection(MoveAction); + } + }break; + case EVRMoveAction::VRMOVEACTION_SetRotation: + { + if (!BaseVRCharacterOwner->SeatInformation.bSitting) + { + /*return */DoMASetRotation(MoveAction); + } + }break; + case EVRMoveAction::VRMOVEACTION_PauseTracking: + { + /*return */DoMAPauseTracking(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_None: + {}break; + default: // All other move actions (CUSTOM) + { + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->OnCustomMoveActionPerformed(MoveAction.MoveAction, MoveAction.MoveActionLoc, MoveAction.MoveActionRot, MoveAction.MoveActionFlags); + } + }break; + } + } + + return true; +} + +bool UVRBaseCharacterMovementComponent::DoMASnapTurn(FVRMoveActionContainer& MoveAction) +{ + if (BaseVRCharacterOwner) + { + FRotator TargetRot = MoveAction.MoveActionRot; + FQuat OrigRot = BaseVRCharacterOwner->GetActorQuat(); + + if (BaseVRCharacterOwner->SeatInformation.bSitting) + { + FRotator DeltaRot(0.f, MoveAction.MoveActionDeltaYaw, 0.f); + TargetRot = ( OrigRot * DeltaRot.Quaternion() ).Rotator(); + } + + FTransform OriginalRelativeTrans = BaseVRCharacterOwner->GetRootComponent()->GetRelativeTransform(); + + bool bRotateAroundCapsule = MoveAction.MoveActionFlags & 0x08; + + if (this->BaseVRCharacterOwner && this->BaseVRCharacterOwner->IsLocallyControlled()) + { + if (this->bUseClientControlRotation) + { + MoveAction.MoveActionLoc = BaseVRCharacterOwner->SetActorRotationVR(TargetRot, false, false, bRotateAroundCapsule); + MoveAction.MoveActionFlags |= 0x04; // Flag that we are using loc only + } + else + { + BaseVRCharacterOwner->SetActorRotationVR(TargetRot, false, false, bRotateAroundCapsule); + } + } + else + { + if (MoveAction.MoveActionFlags & 0x04) + { + BaseVRCharacterOwner->SetActorLocation(BaseVRCharacterOwner->GetActorLocation() + MoveAction.MoveActionLoc); + } + else + { + BaseVRCharacterOwner->SetActorRotationVR(TargetRot, false, false, bRotateAroundCapsule); + } + } + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (BaseVRCharacterOwner->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement((TargetRot.Quaternion() * OrigRot.Inverse()).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + // If we are flagged to teleport the grips + if (MoveAction.MoveActionFlags & 0x01 || MoveAction.MoveActionFlags & 0x02) + { + BaseVRCharacterOwner->NotifyOfTeleport(MoveAction.MoveActionFlags & 0x02); + } + + if (BaseVRCharacterOwner->SeatInformation.bSitting) + { + BaseVRCharacterOwner->SeatInformation.StoredTargetTransform = (OriginalRelativeTrans.Inverse() * BaseVRCharacterOwner->GetRootComponent()->GetRelativeTransform()) * BaseVRCharacterOwner->SeatInformation.StoredTargetTransform; + if (BaseVRCharacterOwner->IsLocallyControlled() && GetNetMode() == ENetMode::NM_Client) + { + BaseVRCharacterOwner->Server_SeatedSnapTurn(MoveAction.MoveActionDeltaYaw); + } + } + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMASetRotation(FVRMoveActionContainer& MoveAction) +{ + bool bRotateAroundCapsule = MoveAction.MoveActionFlags & 0x08; + + if (BaseVRCharacterOwner) + { + FTransform OriginalRelativeTrans = BaseVRCharacterOwner->GetRootComponent()->GetRelativeTransform(); + + FRotator TargetRot(0.f, MoveAction.MoveActionRot.Yaw, 0.f); + if (this->BaseVRCharacterOwner && this->BaseVRCharacterOwner->IsLocallyControlled()) + { + if (this->bUseClientControlRotation) + { + MoveAction.MoveActionLoc = BaseVRCharacterOwner->SetActorRotationVR(TargetRot, true); + MoveAction.MoveActionFlags |= 0x04; // Flag that we are using loc only + } + else + { + BaseVRCharacterOwner->SetActorRotationVR(TargetRot, true, true, bRotateAroundCapsule); + } + } + else + { + if (MoveAction.MoveActionFlags & 0x04) + { + BaseVRCharacterOwner->SetActorLocation(BaseVRCharacterOwner->GetActorLocation() + MoveAction.MoveActionLoc); + } + else + { + BaseVRCharacterOwner->SetActorRotationVR(TargetRot, true, true, bRotateAroundCapsule); + } + } + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (BaseVRCharacterOwner->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement(FRotator(0.f, MoveAction.MoveActionRot.Pitch, 0.f).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + // If we are flagged to teleport the grips + if (MoveAction.MoveActionFlags & 0x01 || MoveAction.MoveActionFlags & 0x02) + { + BaseVRCharacterOwner->NotifyOfTeleport(MoveAction.MoveActionFlags & 0x02); + } + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMATeleport(FVRMoveActionContainer& MoveAction) +{ + if (BaseVRCharacterOwner) + { + AController* OwningController = BaseVRCharacterOwner->GetController(); + + if (!OwningController) + { + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_None; + return false; + } + + bool bSkipEncroachmentCheck = MoveAction.MoveActionFlags & 0x01; //MoveAction.MoveActionRot.Roll > 0.0f; + FRotator TargetRot(0.f, MoveAction.MoveActionRot.Yaw, 0.f); + BaseVRCharacterOwner->TeleportTo(MoveAction.MoveActionLoc, TargetRot, false, bSkipEncroachmentCheck); + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (BaseVRCharacterOwner->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement(FRotator(0.f, MoveAction.MoveActionRot.Pitch, 0.f).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + if (BaseVRCharacterOwner->bUseControllerRotationYaw) + OwningController->SetControlRotation(TargetRot); + + return true; + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMAStopAllMovement(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter * OwningCharacter = Cast(GetCharacterOwner())) + { + this->StopMovementImmediately(); + return true; + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMASetGravityDirection(FVRMoveActionContainer& MoveAction) +{ + bool bOrientToNewGravity = MoveAction.MoveActionFlags > 0; + return SetCharacterToNewGravity(MoveAction.MoveActionVel, bOrientToNewGravity); +} + +bool UVRBaseCharacterMovementComponent::DoMAPauseTracking(FVRMoveActionContainer& MoveAction) +{ + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->bTrackingPaused = MoveAction.MoveActionFlags > 0; + BaseVRCharacterOwner->PausedTrackingLoc = MoveAction.MoveActionLoc; + BaseVRCharacterOwner->PausedTrackingRot = MoveAction.MoveActionRot.Yaw; + return true; + } + return false; +} + +void UVRBaseCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations) +{ + switch (static_cast(CustomMovementMode)) + { + case EVRCustomMovementMode::VRMOVE_Climbing: + PhysCustom_Climbing(deltaTime, Iterations); + break; + case EVRCustomMovementMode::VRMOVE_LowGrav: + PhysCustom_LowGrav(deltaTime, Iterations); + break; + case EVRCustomMovementMode::VRMOVE_Seated: + break; + default: + Super::PhysCustom(deltaTime, Iterations); + break; + } +} + +bool UVRBaseCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + return StepUp(GravDir, Delta, InHit, OutStepDownResult); +} + +void UVRBaseCharacterMovementComponent::PhysCustom_Climbing(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Skip calling into BP if we aren't locally controlled - *EDIT* MOVED TO TICKCOMPONENT to avoid batched movement issues + /*if (CharacterOwner->IsLocallyControlled()) + { + // Allow the player to run updates on the climb logic for CustomVRInputVector + if (AVRBaseCharacter * characterOwner = Cast(CharacterOwner)) + { + characterOwner->UpdateClimbingMovement(deltaTime); + } + }*/ + + + // I am forcing this to 0 to avoid some legacy velocity coming out of other movement modes, climbing should only be direct movement anyway. + Velocity = FVector::ZeroVector; + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = /*(Velocity * deltaTime) + */CustomVRInputVector; + FVector Delta = Adjusted + AdditionalVRInputVector; + bool bZeroDelta = Delta.IsNearlyZero(); + + FStepDownResult StepDownResult; + + // Instead of remaking the step up function, temp assign a custom step height and then fall back to the old one afterward + // This isn't the "proper" way to do it, but it saves on re-making stepup() for both vr characters seperatly (due to different hmd injection) + float OldMaxStepHeight = MaxStepHeight; + MaxStepHeight = VRClimbingStepHeight; + bool bSteppedUp = false; + + if (!bZeroDelta) + { + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = (CustomVRInputVector).GetSafeNormal();//Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + //bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + float stepZ = UpdatedComponent->GetComponentLocation().Z; + + // Making it easier to step up here with the multiplier, helps avoid falling back off + if(bClampClimbingStepUp) + bSteppedUp = VRClimbStepUp(GravDir, ((Adjusted.GetClampedToMaxSize2D(VRClimbingStepUpMaxSize) * VRClimbingStepUpMultiplier) + AdditionalVRInputVector) * (1.f - Hit.Time), Hit, &StepDownResult); + else + bSteppedUp = VRClimbStepUp(GravDir, ((Adjusted * VRClimbingStepUpMultiplier) + AdditionalVRInputVector) * (1.f - Hit.Time), Hit, &StepDownResult); + + if (bSteppedUp && OnPerformClimbingStepUp.IsBound()) + { + FVector finalLoc = UpdatedComponent->GetComponentLocation(); + + // Rewind the step up, the end user wants to handle it instead + ScopedStepUpMovement.RevertMove(); + + // Revert to old max step height + MaxStepHeight = OldMaxStepHeight; + + OnPerformClimbingStepUp.Broadcast(finalLoc); + return; + } + + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + } + + // Revert to old max step height + MaxStepHeight = OldMaxStepHeight; + + if (bSteppedUp) + { + if (AVRBaseCharacter * ownerCharacter = Cast(CharacterOwner)) + { + if (SetDefaultPostClimbMovementOnStepUp) + { + // Takes effect next frame, this allows server rollback to correctly handle auto step up + SetReplicatedMovementMode(DefaultPostClimbMovement); + // Before doing this the server could rollback the client from a bad step up and leave it hanging in climb mode + // This way the rollback replay correctly sets the movement mode from the step up request + + Velocity = FVector::ZeroVector; + } + + // Notify the end user that they probably want to stop gripping now + ownerCharacter->OnClimbingSteppedUp(); + } + } + + // Update floor. + // StepUp might have already done it for us. + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL); + } + + if (CurrentFloor.IsWalkableFloor()) + { + if(CurrentFloor.GetDistanceToFloor() < (MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2) + AdjustFloorHeight(); + + // This was causing based movement to apply to climbing + //SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (CurrentFloor.HitResult.bStartPenetrating) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, deltaTime); + } + + if(!bSteppedUp || !SetDefaultPostClimbMovementOnStepUp) + { + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) - AdditionalVRInputVector) / deltaTime).GetClampedToMaxSize(VRClimbingMaxReleaseVelocitySize); + } + } +} + +void UVRBaseCharacterMovementComponent::PhysCustom_LowGrav(float deltaTime, int32 Iterations) +{ + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Skip calling into BP if we aren't locally controlled + if (CharacterOwner->IsLocallyControlled()) + { + // Allow the player to run updates on the push logic for CustomVRInputVector + if (AVRBaseCharacter * characterOwner = Cast(CharacterOwner)) + { + characterOwner->UpdateLowGravMovement(deltaTime); + } + } + + float Friction = 0.0f; + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + //RestorePreAdditiveVRMotionVelocity(); + + // If we are not in the default physics volume then accept the custom fluid friction setting + // I set this by default to be ignored as many will not alter the default fluid friction + if(!VRLowGravIgnoresDefaultFluidFriction || GetWorld()->GetDefaultPhysicsVolume() != GetPhysicsVolume()) + Friction = 0.5f * GetPhysicsVolume()->FluidFriction; + + CalcVelocity(deltaTime, Friction, true, 0.0f); + + // Adding in custom VR input vector here, can be used for custom movement during it + // AddImpulse is not multiplayer compatible client side + //Velocity += CustomVRInputVector; + + ApplyVRMotionToVelocity(deltaTime); + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = (Velocity * deltaTime); + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted/* + AdditionalVRInputVector*/, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + // Still running step up with grav dir + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit); + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) /* - AdditionalVRInputVector*/) / deltaTime) * VRLowGravWallFrictionScaler; + } + } + else + { + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) /* - AdditionalVRInputVector*/) / deltaTime); + } + } + + RestorePreAdditiveVRMotionVelocity(); +} + + +void UVRBaseCharacterMovementComponent::SetClimbingMode(bool bIsClimbing) +{ + if (bIsClimbing) + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_VRMOVE_Climbing; + else + VRReplicatedMovementMode = DefaultPostClimbMovement; +} + +void UVRBaseCharacterMovementComponent::SetReplicatedMovementMode(EVRConjoinedMovementModes NewMovementMode) +{ + // Only have up to 15 that it can go up to, the previous 7 index's are used up for std movement modes + VRReplicatedMovementMode = NewMovementMode; +} + +EVRConjoinedMovementModes UVRBaseCharacterMovementComponent::GetReplicatedMovementMode() +{ + if (MovementMode == EMovementMode::MOVE_Custom) + { + return (EVRConjoinedMovementModes)((int8)CustomMovementMode + (int8)EVRConjoinedMovementModes::C_VRMOVE_Climbing); + } + else + return (EVRConjoinedMovementModes)MovementMode.GetValue(); +} + +void UVRBaseCharacterMovementComponent::ApplyNetworkMovementMode(const uint8 ReceivedMode) +{ + if (CharacterOwner->GetLocalRole() != ENetRole::ROLE_SimulatedProxy) + { + const uint8 CurrentPackedMovementMode = PackNetworkMovementMode(); + if (CurrentPackedMovementMode != ReceivedMode) + { + TEnumAsByte NetMovementMode(MOVE_None); + TEnumAsByte NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ReceivedMode, NetMovementMode, NetCustomMode, NetGroundMode); + + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + return; // Don't rollback custom movement modes, we set the server to trust the client on them now so the server should get corrected + } + } + } + + Super::ApplyNetworkMovementMode(ReceivedMode); +} + +void UVRBaseCharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + + BaseVRCharacterOwner = Cast(CharacterOwner); +} + + +void UVRBaseCharacterMovementComponent::PerformMovement(float DeltaSeconds) +{ + // Scope these, they nest with Outer references so it should work fine + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + // This moves it into update scope + if (bRunControlRotationInMovementComponent && CharacterOwner->IsLocallyControlled()) + { + if (BaseVRCharacterOwner && BaseVRCharacterOwner->OwningVRPlayerController) + { + BaseVRCharacterOwner->OwningVRPlayerController->RotationInput = BaseVRCharacterOwner->OwningVRPlayerController->LastRotationInput; + BaseVRCharacterOwner->OwningVRPlayerController->UpdateRotation(DeltaSeconds); + BaseVRCharacterOwner->OwningVRPlayerController->LastRotationInput = FRotator::ZeroRotator; + BaseVRCharacterOwner->OwningVRPlayerController->RotationInput = FRotator::ZeroRotator; + } + } + + // Apply any replicated movement modes that are pending + ApplyReplicatedMovementMode(VRReplicatedMovementMode, true); + + // Handle move actions here - Should be scoped + CheckForMoveAction(); + + // Clear out this flag prior to movement so we can see if it gets changed + bIsInPushBack = false; + + + // Handle collision swapping if we aren't doing locomotion based movement + if (BaseVRCharacterOwner->VRRootReference->bUseWalkingCollisionOverride && !BaseVRCharacterOwner->bRetainRoomscale) + { + bool bAllowWalkingCollision = false; + if (MovementMode == EMovementMode::MOVE_Walking || MovementMode == EMovementMode::MOVE_NavWalking) + bAllowWalkingCollision = true; + + BaseVRCharacterOwner->VRRootReference->SetCollisionOverride(bAllowWalkingCollision && GetCurrentAcceleration().IsNearlyZero()); + } + + Super::PerformMovement(DeltaSeconds); + + EndPushBackNotification(); // Check if we need to notify of ending pushback + + // Make sure these are cleaned out for the next frame + //AdditionalVRInputVector = FVector::ZeroVector; + //CustomVRInputVector = FVector::ZeroVector; + + // Only clear it here if we are the server, the client clears it later + if (CharacterOwner->GetLocalRole() == ROLE_Authority) + { + MoveActionArray.Clear(); + } +} + +void UVRBaseCharacterMovementComponent::OnClientCorrectionReceived(class FNetworkPredictionData_Client_Character& ClientData, float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode, FVector ServerGravityDirection) +{ + Super::OnClientCorrectionReceived(ClientData, TimeStamp, NewLocation, NewVelocity, NewBase, NewBaseBoneName, bHasBase, bBaseRelativePosition, ServerMovementMode, ServerGravityDirection); + + + // If we got corrected then lets teleport our grips, this means that we were out of sync with the server or the server moved us + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->OnCharacterNetworkCorrected_Bind.Broadcast(); + BaseVRCharacterOwner->LeftMotionController->TeleportMoveGrips(false, false); + BaseVRCharacterOwner->RightMotionController->TeleportMoveGrips(false, false); + //BaseVRCharacterOwner->NotifyOfTeleport(false); + } +} + +void UVRBaseCharacterMovementComponent::SimulatedTick(float DeltaSeconds) +{ + //return Super::SimulatedTick(DeltaSeconds); + + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_CharacterMovementSimulated); + checkSlow(CharacterOwner != nullptr); + + // If we are playing a RootMotion AnimMontage. + if (CharacterOwner->IsPlayingNetworkedRootMotionMontage()) + { + bWasSimulatingRootMotion = true; + UE_LOG(LogRootMotion, Verbose, TEXT("UCharacterMovementComponent::SimulatedTick")); + + // Tick animations before physics. + if (CharacterOwner && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaSeconds); + + // Make sure animation didn't trigger an event that destroyed us + if (!HasValidData()) + { + return; + } + } + + const FQuat OldRotationQuat = UpdatedComponent->GetComponentQuat(); + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + + USkeletalMeshComponent* Mesh = CharacterOwner->GetMesh(); + const FVector SavedMeshRelativeLocation = Mesh ? Mesh->GetRelativeLocation() : FVector::ZeroVector; + + + if (RootMotionParams.bHasRootMotion) + { + SimulateRootMotion(DeltaSeconds, RootMotionParams.GetRootMotionTransform()); + +#if !(UE_BUILD_SHIPPING) + // debug + /*if (CharacterOwner && false) + { + const FRotator OldRotation = OldRotationQuat.Rotator(); + const FRotator NewRotation = UpdatedComponent->GetComponentRotation(); + const FVector NewLocation = UpdatedComponent->GetComponentLocation(); + DrawDebugCoordinateSystem(GetWorld(), CharacterOwner->GetMesh()->GetComponentLocation() + FVector(0, 0, 1), NewRotation, 50.f, false); + DrawDebugLine(GetWorld(), OldLocation, NewLocation, FColor::Red, false, 10.f); + + UE_LOG(LogRootMotion, Log, TEXT("UCharacterMovementComponent::SimulatedTick DeltaMovement Translation: %s, Rotation: %s, MovementBase: %s"), + *(NewLocation - OldLocation).ToCompactString(), *(NewRotation - OldRotation).GetNormalized().ToCompactString(), *GetNameSafe(CharacterOwner->GetMovementBase())); + }*/ +#endif // !(UE_BUILD_SHIPPING) + } + + // then, once our position is up to date with our animation, + // handle position correction if we have any pending updates received from the server. + if (CharacterOwner && (CharacterOwner->RootMotionRepMoves.Num() > 0)) + { + CharacterOwner->SimulatedRootMotionPositionFixup(DeltaSeconds); + } + + if (!bNetworkSmoothingComplete && (NetworkSmoothingMode == ENetworkSmoothingMode::Linear)) + { + // Same mesh with different rotation? + const FQuat NewCapsuleRotation = UpdatedComponent->GetComponentQuat(); + if (Mesh == CharacterOwner->GetMesh() && !NewCapsuleRotation.Equals(OldRotationQuat, 1e-6f) && ClientPredictionData) + { + // Smoothing should lerp toward this new rotation target, otherwise it will just try to go back toward the old rotation. + ClientPredictionData->MeshRotationTarget = NewCapsuleRotation; + Mesh->SetRelativeLocationAndRotation(SavedMeshRelativeLocation, CharacterOwner->GetBaseRotationOffset()); + } + } + } + else if (CurrentRootMotion.HasActiveRootMotionSources()) + { + // We have root motion sources and possibly animated root motion + bWasSimulatingRootMotion = true; + UE_LOG(LogRootMotion, Verbose, TEXT("UCharacterMovementComponent::SimulatedTick")); + + // If we have RootMotionRepMoves, find the most recent important one and set position/rotation to it + bool bCorrectedToServer = false; + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FQuat OldRotation = UpdatedComponent->GetComponentQuat(); + if (CharacterOwner->RootMotionRepMoves.Num() > 0) + { + // Move Actor back to position of that buffered move. (server replicated position). + FSimulatedRootMotionReplicatedMove& RootMotionRepMove = CharacterOwner->RootMotionRepMoves.Last(); + if (CharacterOwner->RestoreReplicatedMove(RootMotionRepMove)) + { + bCorrectedToServer = true; + } + Acceleration = RootMotionRepMove.RootMotion.Acceleration; + + CharacterOwner->PostNetReceiveVelocity(RootMotionRepMove.RootMotion.LinearVelocity); + LastUpdateVelocity = RootMotionRepMove.RootMotion.LinearVelocity; + + // Convert RootMotionSource Server IDs -> Local IDs in AuthoritativeRootMotion and cull invalid + // so that when we use this root motion it has the correct IDs + ConvertRootMotionServerIDsToLocalIDs(CurrentRootMotion, RootMotionRepMove.RootMotion.AuthoritativeRootMotion, RootMotionRepMove.Time); + RootMotionRepMove.RootMotion.AuthoritativeRootMotion.CullInvalidSources(); + + // Set root motion states to that of repped in state + CurrentRootMotion.UpdateStateFrom(RootMotionRepMove.RootMotion.AuthoritativeRootMotion, true); + + // Clear out existing RootMotionRepMoves since we've consumed the most recent + UE_LOG(LogRootMotion, Log, TEXT("\tClearing old moves in SimulatedTick (%d)"), CharacterOwner->RootMotionRepMoves.Num()); + CharacterOwner->RootMotionRepMoves.Reset(); + } + + // Update replicated gravity direction + if (bNetworkGravityDirectionChanged) + { + SetGravityDirection(CharacterOwner->GetReplicatedGravityDirection()); + bNetworkGravityDirectionChanged = false; + } + + // Update replicated movement mode. + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + + // Perform movement + PerformMovement(DeltaSeconds); + + // After movement correction, smooth out error in position if any. + if (bCorrectedToServer || CurrentRootMotion.NeedsSimulatedSmoothing()) + { + SmoothCorrection(OldLocation, OldRotation, UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat()); + } + } + // Not playing RootMotion AnimMontage + else + { + // if we were simulating root motion, we've been ignoring regular ReplicatedMovement updates. + // If we're not simulating root motion anymore, force us to sync our movement properties. + // (Root Motion could leave Velocity out of sync w/ ReplicatedMovement) + if (bWasSimulatingRootMotion) + { + CharacterOwner->RootMotionRepMoves.Empty(); + CharacterOwner->OnRep_ReplicatedMovement(); + CharacterOwner->OnRep_ReplicatedBasedMovement(); + SetGravityDirection(GetCharacterOwner()->GetReplicatedGravityDirection()); + ApplyNetworkMovementMode(GetCharacterOwner()->GetReplicatedMovementMode()); + } + + if (CharacterOwner->IsReplicatingMovement() && UpdatedComponent) + { + //USkeletalMeshComponent* Mesh = CharacterOwner->GetMesh(); + //const FVector SavedMeshRelativeLocation = Mesh ? Mesh->GetRelativeLocation() : FVector::ZeroVector; + //const FQuat SavedCapsuleRotation = UpdatedComponent->GetComponentQuat(); + const bool bPreventMeshMovement = !bNetworkSmoothingComplete; + + // Avoid moving the mesh during movement if SmoothClientPosition will take care of it. + if(NetworkSmoothingMode != ENetworkSmoothingMode::Disabled) + { + const FScopedPreventAttachedComponentMove PreventMeshMove(bPreventMeshMovement ? BaseVRCharacterOwner->NetSmoother : nullptr); + //const FScopedPreventAttachedComponentMove PreventMeshMovement(bPreventMeshMovement ? Mesh : nullptr); + if (CharacterOwner->IsPlayingRootMotion()) + { + // Update replicated gravity direction + if (bNetworkGravityDirectionChanged) + { + SetGravityDirection(CharacterOwner->GetReplicatedGravityDirection()); + bNetworkGravityDirectionChanged = false; + } + + // Update replicated movement mode. + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + + PerformMovement(DeltaSeconds); + } + else + { + // Moved this var into the VRChar to control smoothing + //if(!bDisableSimulatedTickWhenSmoothingMovement) + SimulateMovement(DeltaSeconds); + } + } + else + { + if (CharacterOwner->IsPlayingRootMotion()) + { + PerformMovement(DeltaSeconds); + } + else + { + SimulateMovement(DeltaSeconds); + } + } + + // With Linear smoothing we need to know if the rotation changes, since the mesh should follow along with that (if it was prevented above). + // This should be rare that rotation changes during simulation, but it can happen when ShouldRemainVertical() changes, or standing on a moving base. + /*const bool bValidateRotation = bPreventMeshMovement && (NetworkSmoothingMode == ENetworkSmoothingMode::Linear); + if (bValidateRotation && UpdatedComponent) + { + // Same mesh with different rotation? + const FQuat NewCapsuleRotation = UpdatedComponent->GetComponentQuat(); + if (Mesh == CharacterOwner->GetMesh() && !NewCapsuleRotation.Equals(SavedCapsuleRotation, 1e-6f) && ClientPredictionData) + { + // Smoothing should lerp toward this new rotation target, otherwise it will just try to go back toward the old rotation. + ClientPredictionData->MeshRotationTarget = NewCapsuleRotation; + Mesh->SetRelativeLocationAndRotation(SavedMeshRelativeLocation, CharacterOwner->GetBaseRotationOffset()); + } + }*/ + } + + if (bWasSimulatingRootMotion) + { + bWasSimulatingRootMotion = false; + } + } + + // Smooth mesh location after moving the capsule above. + if (!bNetworkSmoothingComplete) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_CharacterMovementSmoothClientPosition); + SmoothClientPosition(DeltaSeconds); + } + else + { + UE_LOG(LogVRBaseCharacterMovement, Verbose, TEXT("Skipping network smoothing for %s."), *GetNameSafe(CharacterOwner)); + } +} + +void UVRBaseCharacterMovementComponent::MoveAutonomous( + float ClientTimeStamp, + float DeltaTime, + uint8 CompressedFlags, + const FVector& NewAccel +) +{ + if (!HasValidData()) + { + return; + } + + UpdateFromCompressedFlags(CompressedFlags); + CharacterOwner->CheckJumpInput(DeltaTime); + + Acceleration = ConstrainInputAcceleration(NewAccel); + Acceleration = Acceleration.GetClampedToMaxSize(GetMaxAcceleration()); + AnalogInputModifier = ComputeAnalogInputModifier(); + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + FQuat OldRotation = UpdatedComponent->GetComponentQuat(); + + if (BaseVRCharacterOwner && NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + OldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetTranslation(); + OldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + } + + const bool bWasPlayingRootMotion = CharacterOwner->IsPlayingRootMotion(); + + PerformMovement(DeltaTime); + + // Check if data is valid as PerformMovement can mark character for pending kill + if (!HasValidData()) + { + return; + } + + // If not playing root motion, tick animations after physics. We do this here to keep events, notifies, states and transitions in sync with client updates. + if (CharacterOwner && !CharacterOwner->bClientUpdating && !CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + if (!bWasPlayingRootMotion) // If we were playing root motion before PerformMovement but aren't anymore, we're on the last frame of anim root motion and have already ticked character + { + TickCharacterPose(DeltaTime); + } + // TODO: SaveBaseLocation() in case tick moves us? + + static const auto CVarEnableQueuedAnimEventsOnServer = IConsoleManager::Get().FindConsoleVariable(TEXT("a.EnableQueuedAnimEventsOnServer")); + if (!CVarEnableQueuedAnimEventsOnServer->GetInt() || CharacterOwner->GetMesh()->ShouldOnlyTickMontages(DeltaTime)) + { + // If we're not doing a full anim graph update on the server, + // trigger events right away, as we could be receiving multiple ServerMoves per frame. + CharacterOwner->GetMesh()->ConditionallyDispatchQueuedAnimEvents(); + } + } + + if (CharacterOwner && UpdatedComponent) + { + // Smooth local view of remote clients on listen servers + static const auto CVarNetEnableListenServerSmoothing = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableListenServerSmoothing")); + if (CVarNetEnableListenServerSmoothing->GetInt() && + CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && + IsNetMode(NM_ListenServer)) + { + SmoothCorrection(OldLocation, OldRotation, UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat()); + } + } +} + +void UVRBaseCharacterMovementComponent::SmoothCorrection(const FVector& OldLocation, const FQuat& OldRotation, const FVector& NewLocation, const FQuat& NewRotation) +{ + + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementSmoothCorrection); + if (!HasValidData()) + { + return; + } + + if (!BaseVRCharacterOwner) + Super::SmoothCorrection(OldLocation, OldRotation, NewLocation, NewRotation); + + // We shouldn't be running this on a server that is not a listen server. + checkSlow(GetNetMode() != NM_DedicatedServer); + + // Only client proxies or remote clients on a listen server should run this code. + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const bool bIsRemoteAutoProxy = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy); + ensure(bIsSimulatedProxy || bIsRemoteAutoProxy); + + // Getting a correction means new data, so smoothing needs to run. + bNetworkSmoothingComplete = false; + + // Handle selected smoothing mode. + if (NetworkSmoothingMode == ENetworkSmoothingMode::Disabled || GetNetMode() == NM_Standalone) + { + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, ETeleportType::TeleportPhysics); + bNetworkSmoothingComplete = true; + } + else if (FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character()) + { + const UWorld* MyWorld = GetWorld(); + if (!ensure(MyWorld != nullptr)) + { + return; + } + + // Handle my custom VR Offset + FVector OldWorldLocation = OldLocation; + FQuat OldWorldRotation = OldRotation; + FVector NewWorldLocation = NewLocation; + FQuat NewWorldRotation = NewRotation; + + if (BaseVRCharacterOwner && NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + if (GetNetMode() < ENetMode::NM_Client) + { + NewWorldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetTranslation(); + NewWorldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + } + else + { + FTransform NewWorldTransform(NewRotation, NewLocation, UpdatedComponent->GetRelativeScale3D()); + FTransform CurrentRelative = BaseVRCharacterOwner->OffsetComponentToWorld.GetRelativeTransform(UpdatedComponent->GetComponentTransform()); + FTransform NewWorld = CurrentRelative * NewWorldTransform; + OldWorldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetLocation(); + OldWorldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + NewWorldLocation = NewWorld.GetLocation(); + NewWorldRotation = NewWorld.GetRotation(); + } + } + + // The mesh doesn't move, but the capsule does so we have a new offset. + FVector NewToOldVector = (OldWorldLocation - NewWorldLocation); + if (bIsNavWalkingOnServer && FMath::Abs(NewToOldVector.Z) < NavWalkingFloorDistTolerance) + { + // ignore smoothing on Z axis + // don't modify new location (local simulation result), since it's probably more accurate than server data + // and shouldn't matter as long as difference is relatively small + NewToOldVector.Z = 0; + } + + const float DistSq = NewToOldVector.SizeSquared(); + if (DistSq > FMath::Square(ClientData->MaxSmoothNetUpdateDist)) + { + ClientData->MeshTranslationOffset = (DistSq > FMath::Square(ClientData->NoSmoothNetUpdateDist)) + ? FVector::ZeroVector + : ClientData->MeshTranslationOffset + ClientData->MaxSmoothNetUpdateDist * NewToOldVector.GetSafeNormal(); + } + else + { + ClientData->MeshTranslationOffset = ClientData->MeshTranslationOffset + NewToOldVector; + } + + UE_LOG(LogVRBaseCharacterMovement, Verbose, TEXT("Proxy %s SmoothCorrection(%.2f)"), *GetNameSafe(CharacterOwner), FMath::Sqrt(DistSq)); + if (NetworkSmoothingMode == ENetworkSmoothingMode::Linear) + { + // #TODO: Get this working in the future? + // I am currently skipping smoothing on rotation operations + if ((!OldRotation.Equals(NewRotation, 1e-5f)))// || Velocity.IsNearlyZero())) + { + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(BaseVRCharacterOwner->bRetainRoomscale ? FVector::ZeroVector : BaseVRCharacterOwner->VRRootReference->GetTargetHeightOffset()); + //BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(FVector::ZeroVector); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + ClientData->MeshTranslationOffset = FVector::ZeroVector; + ClientData->MeshRotationOffset = ClientData->MeshRotationTarget; + bNetworkSmoothingComplete = true; + } + else + { + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + + // Remember the current and target rotation, we're going to lerp between them + ClientData->OriginalMeshRotationOffset = OldRotation; + ClientData->MeshRotationOffset = OldRotation; + ClientData->MeshRotationTarget = NewRotation; + + // Move the capsule, but not the mesh. + // Note: we don't change rotation, we lerp towards it in SmoothClientPosition. + if (NewLocation != OldLocation) + { + const FScopedPreventAttachedComponentMove PreventMeshMove(BaseVRCharacterOwner->NetSmoother); + UpdatedComponent->SetWorldLocation(NewLocation, false, nullptr, GetTeleportType()); + } + } + } + else + { + // #TODO: Get this working in the future? + // I am currently skipping smoothing on rotation operations + /*if ((!OldRotation.Equals(NewRotation, 1e-5f)))// || Velocity.IsNearlyZero())) + { + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(BaseVRCharacterOwner->bRetainRoomscale ? FVector::ZeroVector : BaseVRCharacterOwner->VRRootReference->GetTargetHeightOffset()); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + ClientData->MeshTranslationOffset = FVector::ZeroVector; + ClientData->MeshRotationOffset = ClientData->MeshRotationTarget; + bNetworkSmoothingComplete = true; + } + else*/ + { + // Calc rotation needed to keep current world rotation after UpdatedComponent moves. + // Take difference between where we were rotated before, and where we're going + ClientData->MeshRotationOffset = FQuat::Identity;// (NewRotation.Inverse() * OldRotation) * ClientData->MeshRotationOffset; + ClientData->MeshRotationTarget = FQuat::Identity; + + const FScopedPreventAttachedComponentMove PreventMeshMove(BaseVRCharacterOwner->NetSmoother); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Update smoothing timestamps + + // If running ahead, pull back slightly. This will cause the next delta to seem slightly longer, and cause us to lerp to it slightly slower. + if (ClientData->SmoothingClientTimeStamp > ClientData->SmoothingServerTimeStamp) + { + const double OldClientTimeStamp = ClientData->SmoothingClientTimeStamp; + ClientData->SmoothingClientTimeStamp = FMath::LerpStable(ClientData->SmoothingServerTimeStamp, OldClientTimeStamp, 0.5); + + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("SmoothCorrection: Pull back client from ClientTimeStamp: %.6f to %.6f, ServerTimeStamp: %.6f for %s"), + OldClientTimeStamp, ClientData->SmoothingClientTimeStamp, ClientData->SmoothingServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + + // Using server timestamp lets us know how much time actually elapsed, regardless of packet lag variance. + double OldServerTimeStamp = ClientData->SmoothingServerTimeStamp; + + if (bIsSimulatedProxy) + { + // This value is normally only updated on the server, however some code paths might try to read it instead of the replicated value so copy it for proxies as well. + ServerLastTransformUpdateTimeStamp = CharacterOwner->GetReplicatedServerLastTransformUpdateTimeStamp(); + } + ClientData->SmoothingServerTimeStamp = ServerLastTransformUpdateTimeStamp; + + // Initial update has no delta. + if (ClientData->LastCorrectionTime == 0) + { + ClientData->SmoothingClientTimeStamp = ClientData->SmoothingServerTimeStamp; + OldServerTimeStamp = ClientData->SmoothingServerTimeStamp; + } + + // Don't let the client fall too far behind or run ahead of new server time. + const double ServerDeltaTime = ClientData->SmoothingServerTimeStamp - OldServerTimeStamp; + const double MaxOffset = ClientData->MaxClientSmoothingDeltaTime; + const double MinOffset = FMath::Min(double(ClientData->SmoothNetUpdateTime), MaxOffset); + + // MaxDelta is the farthest behind we're allowed to be after receiving a new server time. + const double MaxDelta = FMath::Clamp(ServerDeltaTime * 1.25, MinOffset, MaxOffset); + ClientData->SmoothingClientTimeStamp = FMath::Clamp(ClientData->SmoothingClientTimeStamp, ClientData->SmoothingServerTimeStamp - MaxDelta, ClientData->SmoothingServerTimeStamp); + + // Compute actual delta between new server timestamp and client simulation. + ClientData->LastCorrectionDelta = ClientData->SmoothingServerTimeStamp - ClientData->SmoothingClientTimeStamp; + ClientData->LastCorrectionTime = MyWorld->GetTimeSeconds(); + + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("SmoothCorrection: WorldTime: %.6f, ServerTimeStamp: %.6f, ClientTimeStamp: %.6f, Delta: %.6f for %s"), + MyWorld->GetTimeSeconds(), ClientData->SmoothingServerTimeStamp, ClientData->SmoothingClientTimeStamp, ClientData->LastCorrectionDelta, *GetNameSafe(CharacterOwner)); + /* + Visualize network smoothing was here, removed it + */ + } +} + +void UVRBaseCharacterMovementComponent::SmoothClientPosition(float DeltaSeconds) +{ + if (!HasValidData() || NetworkSmoothingMode == ENetworkSmoothingMode::Disabled) + { + return; + } + + // We shouldn't be running this on a server that is not a listen server. + checkSlow(GetNetMode() != NM_DedicatedServer); + checkSlow(GetNetMode() != NM_Standalone); + + // Only client proxies or remote clients on a listen server should run this code. + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const bool bIsRemoteAutoProxy = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy); + if (!ensure(bIsSimulatedProxy || bIsRemoteAutoProxy)) + { + return; + } + + SmoothClientPosition_Interpolate(DeltaSeconds); + + //SmoothClientPosition_UpdateVisuals(); No mesh, don't bother to run this + SmoothClientPosition_UpdateVRVisuals(); +} + +void UVRBaseCharacterMovementComponent::SmoothClientPosition_UpdateVRVisuals() +{ + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementSmoothClientPosition_Visual); + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (!BaseVRCharacterOwner || !ClientData) + return; + + if (ClientData) + { + if (NetworkSmoothingMode == ENetworkSmoothingMode::Linear) + { + // Erased most of the code here, check back in later + const USceneComponent* MeshParent = BaseVRCharacterOwner->NetSmoother->GetAttachParent(); + FVector MeshParentScale = MeshParent != nullptr ? MeshParent->GetComponentScale() : FVector(1.0f, 1.0f, 1.0f); + + MeshParentScale.X = FMath::IsNearlyZero(MeshParentScale.X) ? 1.0f : MeshParentScale.X; + MeshParentScale.Y = FMath::IsNearlyZero(MeshParentScale.Y) ? 1.0f : MeshParentScale.Y; + MeshParentScale.Z = FMath::IsNearlyZero(MeshParentScale.Z) ? 1.0f : MeshParentScale.Z; + + const FVector NewRelLocation = ClientData->MeshRotationOffset.UnrotateVector(ClientData->MeshTranslationOffset);// + CharacterOwner->GetBaseTranslationOffset(); + + FVector HeightOffset = (BaseVRCharacterOwner->bRetainRoomscale ? FVector::ZeroVector : BaseVRCharacterOwner->VRRootReference->GetTargetHeightOffset()); + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(NewRelLocation + HeightOffset); + } + else if (NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + const USceneComponent* MeshParent = BaseVRCharacterOwner->NetSmoother->GetAttachParent(); + FVector MeshParentScale = MeshParent != nullptr ? MeshParent->GetComponentScale() : FVector(1.0f, 1.0f, 1.0f); + + MeshParentScale.X = FMath::IsNearlyZero(MeshParentScale.X) ? 1.0f : MeshParentScale.X; + MeshParentScale.Y = FMath::IsNearlyZero(MeshParentScale.Y) ? 1.0f : MeshParentScale.Y; + MeshParentScale.Z = FMath::IsNearlyZero(MeshParentScale.Z) ? 1.0f : MeshParentScale.Z; + + // Adjust mesh location and rotation + const FVector NewRelTranslation = (UpdatedComponent->GetComponentToWorld().InverseTransformVectorNoScale(ClientData->MeshTranslationOffset) / MeshParentScale);// +CharacterOwner->GetBaseTranslationOffset(); + const FQuat NewRelRotation = ClientData->MeshRotationOffset;// *CharacterOwner->GetBaseRotationOffset(); + //Basechar->NetSmoother->SetRelativeLocation(NewRelTranslation); + + FVector HeightOffset = BaseVRCharacterOwner->bRetainRoomscale ? FVector::ZeroVector : BaseVRCharacterOwner->VRRootReference->GetTargetHeightOffset(); + BaseVRCharacterOwner->NetSmoother->SetRelativeLocationAndRotation(NewRelTranslation + HeightOffset, NewRelRotation); + } + else + { + // Unhandled mode + } + } +} + +void UVRBaseCharacterMovementComponent::SetHasRequestedVelocity(bool bNewHasRequestedVelocity) +{ + bHasRequestedVelocity = bNewHasRequestedVelocity; +} + +bool UVRBaseCharacterMovementComponent::IsClimbing() const +{ + return ((MovementMode == MOVE_Custom) && (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)) && UpdatedComponent; +} + +FVector UVRBaseCharacterMovementComponent::RewindVRMovement() +{ + RewindVRRelativeMovement(); + return AdditionalVRInputVector; +} + +FVector UVRBaseCharacterMovementComponent::GetCustomInputVector() +{ + return CustomVRInputVector; +} + +void UVRBaseCharacterMovementComponent::UpdateFromCompressedFlags(uint8 Flags) +{ + // If is a custom or VR custom movement mode + //int32 MovementFlags = (Flags >> 2) & 15; + //VRReplicatedMovementMode = (EVRConjoinedMovementModes)MovementFlags; + + //bWantsToSnapTurn = ((Flags & FSavedMove_VRBaseCharacter::FLAG_SnapTurn) != 0); + + Super::UpdateFromCompressedFlags(Flags); +} + +FVector UVRBaseCharacterMovementComponent::RoundDirectMovement(FVector InMovement) const +{ + // Match FVector_NetQuantize100 (2 decimal place of precision). + InMovement.X = FMath::RoundToFloat(InMovement.X * 100.f) / 100.f; + InMovement.Y = FMath::RoundToFloat(InMovement.Y * 100.f) / 100.f; + InMovement.Z = FMath::RoundToFloat(InMovement.Z * 100.f) / 100.f; + return InMovement; +} + +void UVRBaseCharacterMovementComponent::SetAutoOrientToFloorNormal(bool bAutoOrient, bool bRevertGravityWhenDisabled) +{ + bAutoOrientToFloorNormal = bAutoOrient; + + if (!bAutoOrientToFloorNormal && bRevertGravityWhenDisabled) + { + //DefaultGravityDirection, could also get world gravity + SetCharacterToNewGravity(FVector(0.0f, 0.0f, -1.0f), true); + } +} + +void UVRBaseCharacterMovementComponent::AutoTraceAndSetCharacterToNewGravity(FHitResult & TargetFloor, float DeltaTime) +{ + if (TargetFloor.Component.IsValid()) + { + // Should we really be tracing complex? (true should maybe be false?) + FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(AutoTraceFloorNormal), /*true*/false); + + FVector TraceStart = BaseVRCharacterOwner->GetVRLocation_Inline(); + FVector Offset = (-UpdatedComponent->GetComponentQuat().GetUpVector()) * (BaseVRCharacterOwner->VRRootReference->GetScaledCapsuleHalfHeight() + 10.0f); + + FHitResult OutHit; + const bool bDidHit = TargetFloor.Component->LineTraceComponent(OutHit, TraceStart, TraceStart + Offset, QueryParams); + + if (bDidHit) + { + FVector NewGravityDir = -OutHit.Normal; + + // Don't run gravity changes within our walkable slope + float AngleOfChange = FMath::Abs(FMath::RadiansToDegrees(acosf(FVector::DotProduct(-OutHit.Normal, GetGravityDirection())))); + if (AngleOfChange > GetWalkableFloorAngle() || AngleOfChange < 0.01f) + { + return; + } + else + { + if (bBlendGravityFloorChanges) + { + // Blend the angle over time + const float Alpha = FMath::Clamp(DeltaTime * FloorOrientationChangeBlendRate, 0.f, 1.f); + NewGravityDir = FMath::Lerp(GetGravityDirection(), NewGravityDir, Alpha); + } + } + + SetCharacterToNewGravity(NewGravityDir, true); + } + } +} + +bool UVRBaseCharacterMovementComponent::SetCharacterToNewGravity(FVector NewGravityDirection, bool bOrientToNewGravity) +{ + // Ensure its normalized + NewGravityDirection.Normalize(); + + if (NewGravityDirection.Equals(GetGravityDirection())) + return false; + + SetGravityDirection(NewGravityDirection); + + if (bOrientToNewGravity && IsValid(BaseVRCharacterOwner)) + { + FQuat CurrentRotQ = UpdatedComponent->GetComponentQuat(); + FQuat DeltaRot = FQuat::FindBetweenNormals(-CurrentRotQ.GetUpVector(), NewGravityDirection); + FRotator NewRot = (DeltaRot * CurrentRotQ)/*.GetNormalized()*/.Rotator(); + AController* OwningController = BaseVRCharacterOwner->GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector OrigLocation = BaseVRCharacterOwner->GetActorLocation(); + FVector PivotPoint = BaseVRCharacterOwner->GetActorTransform().InverseTransformPosition(BaseVRCharacterOwner->GetVRLocation_Inline()); + //(bRotateAroundCapsule ? GetVRLocation_Inline() : BaseVRCharacterOwner->GetProjectedVRLocation()); + + + // Offset to the floor + //PivotPoint.Z = -BaseVRCharacterOwner->VRRootReference->GetUnscaledCapsuleHalfHeight(); + PivotPoint.Z = 0.0f; + + // Need to seperate out each element for the control rotation + FRotator OrigRotation = BaseVRCharacterOwner->bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : BaseVRCharacterOwner->GetActorRotation(); + + NewRotation = NewRot; + + NewLocation = OrigLocation + OrigRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (BaseVRCharacterOwner->bUseControllerRotationYaw && OwningController) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + MoveUpdatedComponent(NewLocation - OrigLocation, NewRotation, /*bSweep*/ false); + //BaseVRCharacterOwner->SetActorLocationAndRotation(NewLocation, NewRotation); + return true; + } + + return false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp new file mode 100644 index 0000000..a263946 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp @@ -0,0 +1,248 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRCharacter.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRCharacter) + +#include "NavigationSystem.h" +#include "VRBPDatatypes.h" +//#include "GripMotionControllerComponent.h" +//#include "VRExpansionFunctionLibrary.h" +//#include "ReplicatedVRCameraComponent.h" +//#include "ParentRelativeAttachmentComponent.h" +#include "VRRootComponent.h" +#include "VRCharacterMovementComponent.h" +#include "GameFramework/Controller.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRPathFollowingComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +DEFINE_LOG_CATEGORY(LogVRCharacter); + +AVRCharacter::AVRCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass(ACharacter::CapsuleComponentName).SetDefaultSubobjectClass(ACharacter::CharacterMovementComponentName)) +{ + VRRootReference = NULL; + if (GetCapsuleComponent()) + { + VRRootReference = Cast(GetCapsuleComponent()); + VRRootReference->SetCapsuleSize(20.0f, 96.0f); + //VRRootReference->VRCapsuleOffset = FVector(-8.0f, 0.0f, 0.0f); + VRRootReference->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + VRRootReference->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); + } + + VRMovementReference = NULL; + if (GetMovementComponent()) + { + VRMovementReference = Cast(GetMovementComponent()); + //AddTickPrerequisiteComponent(this->GetCharacterMovement()); + } +} + + +FVector AVRCharacter::GetTeleportLocation(FVector OriginalLocation) +{ + if (!bRetainRoomscale) + { + return OriginalLocation + FVector(0.f, 0.f, VRRootReference->GetScaledCapsuleHalfHeight()); + } + else + { + FVector modifier = VRRootReference->OffsetComponentToWorld.GetLocation() - this->GetActorLocation(); + modifier.Z = 0.0f; // Null out Z + return OriginalLocation - modifier; + } +} + +bool AVRCharacter::TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest, bool bNoCheck) +{ + bool bTeleportSucceeded = Super::TeleportTo(DestLocation, DestRotation, bIsATest, bNoCheck); + + if (bTeleportSucceeded) + { + NotifyOfTeleport(); + } + + return bTeleportSucceeded; +} + +FVector AVRCharacter::GetNavAgentLocation() const +{ + FVector AgentLocation = FNavigationSystem::InvalidLocation; + + if (GetCharacterMovement() != nullptr) + { + if (VRMovementReference) + { + AgentLocation = VRMovementReference->GetActorFeetLocationVR(); + } + else + AgentLocation = GetCharacterMovement()->GetActorFeetLocation(); + } + + if (FNavigationSystem::IsValidLocation(AgentLocation) == false /*&& GetCapsuleComponent() != nullptr*/) + { + if (VRRootReference) + { + AgentLocation = VRRootReference->OffsetComponentToWorld.GetLocation() - FVector(0, 0, VRRootReference->GetScaledCapsuleHalfHeight()); + } + else if(GetCapsuleComponent() != nullptr) + AgentLocation = GetActorLocation() - FVector(0, 0, GetCapsuleComponent()->GetScaledCapsuleHalfHeight()); + } + + return AgentLocation; +} + +void AVRCharacter::ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf FilterClass, bool bAllowPartialPaths) +{ + UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent(Controller->GetWorld()) : nullptr; + if (NavSys == nullptr || Controller == nullptr ) + { + UE_LOG(LogVRCharacter, Warning, TEXT("UVRCharacter::ExtendedSimpleMoveToLocation called for NavSys:%s Controller:%s (if any of these is None then there's your problem"), + *GetNameSafe(NavSys), *GetNameSafe(Controller)); + return; + } + + UPathFollowingComponent* PFollowComp = nullptr; + //Controller->InitNavigationControl(PFollowComp); + + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass(); + if (PFollowComp == nullptr) + { + PFollowComp = NewObject(Controller); + PFollowComp->RegisterComponentWithWorld(Controller->GetWorld()); + PFollowComp->Initialize(); + } + } + + if (PFollowComp == nullptr) + { + UE_LOG(LogVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - No PathFollowingComponent Found")); + return; + } + + if (!PFollowComp->IsPathFollowingAllowed()) + { + UE_LOG(LogVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - Path Following Movement Is Not Set To Allowed")); + return; + } + + EPathFollowingReachMode ReachMode; + if (bStopOnOverlap) + ReachMode = EPathFollowingReachMode::OverlapAgent; + else + ReachMode = EPathFollowingReachMode::ExactLocation; + + bool bAlreadyAtGoal = false; + + if(UVRPathFollowingComponent * pathcomp = Cast(PFollowComp)) + bAlreadyAtGoal = pathcomp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + else + bAlreadyAtGoal = PFollowComp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + + // script source, keep only one move request at time + if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + if (GetNetMode() == ENetMode::NM_Client) + { + // Stop the movement here, not keeping the velocity because it bugs out for clients, might be able to fix. + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, /*bAlreadyAtGoal ? */EPathFollowingVelocityMode::Reset /*: EPathFollowingVelocityMode::Keep*/); + } + else + { + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep); + } + } + + if (bAlreadyAtGoal) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Success); + } + else + { + const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef()); + if (NavData) + { + FPathFindingQuery Query(Controller, *NavData, Controller->GetNavAgentLocation(), GoalLocation); + FPathFindingResult Result = NavSys->FindPathSync(Query); + if (Result.IsSuccessful()) + { + FAIMoveRequest MoveReq(GoalLocation); + MoveReq.SetUsePathfinding(bUsePathfinding); + MoveReq.SetAllowPartialPath(bAllowPartialPaths); + MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation); + MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); + MoveReq.SetAcceptanceRadius(AcceptanceRadius); + MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); + MoveReq.SetCanStrafe(bCanStrafe); + MoveReq.SetReachTestIncludesGoalRadius(true); + + PFollowComp->RequestMove(/*FAIMoveRequest(GoalLocation)*/MoveReq, Result.Path); + } + else if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid); + } + } + } +} + +FVector AVRCharacter::GetTargetHeightOffset() +{ + return bRetainRoomscale ? FVector::ZeroVector : VRRootReference->GetTargetHeightOffset(); +} + +void AVRCharacter::RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) +{ + if (VRRootReference) + { + VRRootReference->GenerateOffsetToWorld(bUpdateBounds, bCalculatePureYaw); + } +} + +void AVRCharacter::SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + if (VRRootReference) + { + VRRootReference->SetCapsuleSizeVR(NewRadius, NewHalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client) + ReplicatedCapsuleHeight.CapsuleHeight = VRRootReference->GetUnscaledCapsuleHalfHeight(); + } + else + { + Super::SetCharacterSizeVR(NewRadius, NewHalfHeight, bUpdateOverlaps); + } +} + +void AVRCharacter::SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + if (VRRootReference) + { + VRRootReference->SetCapsuleHalfHeightVR(HalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client) + ReplicatedCapsuleHeight.CapsuleHeight = VRRootReference->GetUnscaledCapsuleHalfHeight(); + } + else + { + Super::SetCharacterHalfHeightVR(HalfHeight, bUpdateOverlaps); + } +} + +FVector AVRCharacter::GetProjectedVRLocation() const +{ + if (VRRootReference) + { + return OffsetComponentToWorld.TransformPosition(-FVector(VRRootReference->VRCapsuleOffset.X, VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + else + { + return AVRBaseCharacter::GetProjectedVRLocation(); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp new file mode 100644 index 0000000..4527c7a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp @@ -0,0 +1,4779 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "VRCharacterMovementComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRCharacterMovementComponent) + +#include "GameFramework/PhysicsVolume.h" +#include "GameFramework/GameNetworkManager.h" +#include "GameFramework/Character.h" +#include "GameFramework/GameState.h" +#include "GameFramework/WorldSettings.h" +#include "Components/PrimitiveComponent.h" +#include "Animation/AnimMontage.h" +#include "DrawDebugHelpers.h" +//#include "PhysicsEngine/DestructibleActor.h" +#include "VRCharacter.h" +#include "VRExpansionFunctionLibrary.h" + +// @todo this is here only due to circular dependency to AIModule. To be removed +#include "Navigation/PathFollowingComponent.h" +#include "AI/Navigation/AvoidanceManager.h" +#include "Components/CapsuleComponent.h" +#include "Components/BrushComponent.h" +//#include "Components/DestructibleComponent.h" + +#include "Engine/DemoNetDriver.h" +#include "Engine/NetworkObjectList.h" + +#include "VRRootComponent.h" +#include "WorldCollision.h" +#include "Runtime/Launch/Resources/Version.h" +#include "GameFramework/CharacterMovementReplication.h" +#include "Interfaces/NetworkPredictionInterface.h" + +//#include "PerfCountersHelpers.h" + +DEFINE_LOG_CATEGORY(LogVRCharacterMovement); + +/** + * Character stats + */ +DECLARE_CYCLE_STAT(TEXT("Char StepUp"), STAT_CharStepUp, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char FindFloor"), STAT_CharFindFloor, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServer"), STAT_CharacterMovementReplicateMoveToServer, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char CallServerMove"), STAT_CharacterMovementCallServerMove, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char CombineNetMove"), STAT_CharacterMovementCombineNetMove, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysWalking"), STAT_CharPhysWalking, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysFalling"), STAT_CharPhysFalling, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysNavWalking"), STAT_CharPhysNavWalking, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char NavProjectPoint"), STAT_CharNavProjectPoint, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char NavProjectLocation"), STAT_CharNavProjectLocation, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char AdjustFloorHeight"), STAT_CharAdjustFloorHeight, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char ProcessLanded"), STAT_CharProcessLanded, STATGROUP_Character); + +namespace CharacterMovementConstants +{ + // MAGIC NUMBERS + const float MAX_STEP_SIDE_ZVR = 0.08f; // maximum z value for the normal on the vertical side of steps + const float SWIMBOBSPEEDVR = -80.f; + const float VERTICAL_SLOPE_NORMAL_ZVR = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface. +} + +// Defines for build configs +#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled. +#define devCodeVR( Code ) checkCode( Code ) +#else +#define devCodeVR(...) +#endif + +// Statics +namespace CharacterMovementComponentStatics +{ + static const FName CrouchTraceName = FName(TEXT("CrouchTrace")); + static const FName ImmersionDepthName = FName(TEXT("MovementComp_Character_ImmersionDepth")); + + static float fRotationCorrectionThreshold = 0.02f; + FAutoConsoleVariableRef CVarRotationCorrectionThreshold( + TEXT("vre.RotationCorrectionThreshold"), + fRotationCorrectionThreshold, + TEXT("Error threshold value before correcting a clients rotation.\n") + TEXT("Rotation is replicated at 2 decimal precision, so values less than 0.01 won't matter."), + ECVF_Default); +} + +void UVRCharacterMovementComponent::StoreSetTrackingPaused(bool bNewTrackingPaused) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_PauseTracking; + MoveAction.MoveActionFlags = bNewTrackingPaused; + MoveAction.MoveActionLoc = VRRootCapsule->curCameraLoc; + MoveAction.MoveActionRot = VRRootCapsule->StoredCameraRotOffset; + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRCharacterMovementComponent::Crouch(bool bClientSimulation) +{ + if (!HasValidData()) + { + return; + } + + if (!bClientSimulation && !CanCrouchInCurrentState()) + { + return; + } + + // See if collision is already at desired size. + if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == GetCrouchedHalfHeight()) + { + if (!bClientSimulation) + { + CharacterOwner->bIsCrouched = true; + } + CharacterOwner->OnStartCrouch(0.f, 0.f); + return; + } + + if (bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) + { + // restore collision size before crouching + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject(); + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()); + + bShrinkProxyCapsule = true; + } + + // Change collision size to crouching dimensions + const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale(); + const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + const float OldUnscaledRadius = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleRadius(); + // Height is not allowed to be smaller than radius. + const float ClampedCrouchedHalfHeight = FMath::Max3(0.f, OldUnscaledRadius, GetCrouchedHalfHeight()); + + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, ClampedCrouchedHalfHeight); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, ClampedCrouchedHalfHeight); + + float HalfHeightAdjust = (OldUnscaledHalfHeight - ClampedCrouchedHalfHeight); + float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + if (!bClientSimulation) + { + // Crouching to a larger height? (this is rare) + if (ClampedCrouchedHalfHeight > OldUnscaledHalfHeight) + { + FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + + FVector capLocation; + if (VRRootCapsule) + { + capLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + } + else + capLocation = UpdatedComponent->GetComponentLocation(); + + const bool bEncroached = GetWorld()->OverlapBlockingTestByChannel(capLocation - FVector(0.f, 0.f, ScaledHalfHeightAdjust), FQuat::Identity, + UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CapsuleParams, ResponseParam); + + // If encroached, cancel + if (bEncroached) + { + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, OldUnscaledHalfHeight); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, OldUnscaledHalfHeight); + return; + } + } + + // Skipping this move down as the VR character's base root doesn't behave the same + // Retain roomscale off also covers it in the capsule size rescale + if (bCrouchMaintainsBaseLocation) + { + // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot. + //UpdatedComponent->MoveComponent(FVector(0.f, 0.f, -ScaledHalfHeightAdjust), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + } + + CharacterOwner->bIsCrouched = true; + } + + bForceNextFloorCheck = true; + + // OnStartCrouch takes the change from the Default size, not the current one (though they are usually the same). + const float MeshAdjust = ScaledHalfHeightAdjust; + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject(); + HalfHeightAdjust = (DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - ClampedCrouchedHalfHeight); + ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + AdjustProxyCapsuleSize(); + CharacterOwner->OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); + + // Don't smooth this change in mesh position + // Set CapsuleSize VR already handles this + /*if (!BaseVRCharacterOwner->bRetainRoomscale) + { + if ((bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) || (IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy)) + { + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (ClientData) + { + ClientData->MeshTranslationOffset -= FVector(0.f, 0.f, MeshAdjust); + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + } + } + }*/ +} + +void UVRCharacterMovementComponent::UnCrouch(bool bClientSimulation) +{ + if (!HasValidData()) + { + return; + } + + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject(); + + // See if collision is already at desired size. + if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()) + { + if (!bClientSimulation) + { + CharacterOwner->bIsCrouched = false; + } + CharacterOwner->OnEndCrouch(0.f, 0.f); + return; + } + + const float CurrentCrouchedHalfHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); + + const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale(); + const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + const float HalfHeightAdjust = DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - OldUnscaledHalfHeight; + const float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + /*const*/ FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + + if (VRRootCapsule) + { + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + } + + // Grow to uncrouched size. + check(CharacterOwner->GetCapsuleComponent()); + + if (!bClientSimulation) + { + // Try to stay in place and see if the larger capsule fits. We use a slightly taller capsule to avoid penetration. + const UWorld* MyWorld = GetWorld(); + const float SweepInflation = UE_KINDA_SMALL_NUMBER * 10.f; + FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + + // Compensate for the difference between current capsule size and standing size + const FCollisionShape StandingCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, -SweepInflation - ScaledHalfHeightAdjust); // Shrink by negative amount, so actually grow it. + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + bool bEncroached = true; + + if (!bCrouchMaintainsBaseLocation) + { + // Expand in place + bEncroached = MyWorld->OverlapBlockingTestByChannel(PawnLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + + if (bEncroached) + { + // Try adjusting capsule position to see if we can avoid encroachment. + if (ScaledHalfHeightAdjust > 0.f) + { + // Shrink to a short capsule, sweep down to base to find where that would hit something, and then try to stand up from there. + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + const float ShrinkHalfHeight = PawnHalfHeight - PawnRadius; + const float TraceDist = PawnHalfHeight - ShrinkHalfHeight; + const FVector Down = FVector(0.f, 0.f, -TraceDist); + + FHitResult Hit(1.f); + const FCollisionShape ShortCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, ShrinkHalfHeight); + const bool bBlockingHit = MyWorld->SweepSingleByChannel(Hit, PawnLocation, PawnLocation + Down, FQuat::Identity, CollisionChannel, ShortCapsuleShape, CapsuleParams); + if (Hit.bStartPenetrating) + { + bEncroached = true; + } + else + { + // Compute where the base of the sweep ended up, and see if we can stand there + const float DistanceToBase = (Hit.Time * TraceDist) + ShortCapsuleShape.Capsule.HalfHeight; + const FVector NewLoc = FVector(PawnLocation.X, PawnLocation.Y, PawnLocation.Z - DistanceToBase + StandingCapsuleShape.Capsule.HalfHeight + SweepInflation + MIN_FLOOR_DIST / 2.f); + bEncroached = MyWorld->OverlapBlockingTestByChannel(NewLoc, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + if (!bEncroached) + { + // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot. + UpdatedComponent->MoveComponent(NewLoc - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + } + } + } + } + } + else + { + // Expand while keeping base location the same. + FVector StandingLocation = PawnLocation + FVector(0.f, 0.f, StandingCapsuleShape.GetCapsuleHalfHeight() - CurrentCrouchedHalfHeight); + bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + + if (bEncroached) + { + if (IsMovingOnGround()) + { + // Something might be just barely overhead, try moving down closer to the floor to avoid it. + const float MinFloorDist = UE_KINDA_SMALL_NUMBER * 10.f; + if (CurrentFloor.bBlockingHit && CurrentFloor.FloorDist > MinFloorDist) + { + StandingLocation.Z -= CurrentFloor.FloorDist - MinFloorDist; + bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + } + } + } + + // Canceling this move because our VR capsule isn't actor based like it expects it to be + if (!bEncroached) + { + // Commit the change in location. + //UpdatedComponent->MoveComponent(StandingLocation - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bForceNextFloorCheck = true; + } + } + + // If still encroached then abort. + if (bEncroached) + { + return; + } + + CharacterOwner->bIsCrouched = false; + } + else + { + bShrinkProxyCapsule = true; + } + + // Now call SetCapsuleSize() to cause touch/untouch events and actually grow the capsule + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true); + + const float MeshAdjust = ScaledHalfHeightAdjust; + AdjustProxyCapsuleSize(); + CharacterOwner->OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); + + // Don't smooth this change in mesh position + /*if ((bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) || (IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy)) + { + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (ClientData) + { + ClientData->MeshTranslationOffset += FVector(0.f, 0.f, MeshAdjust); + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + } + }*/ +} + +FNetworkPredictionData_Client* UVRCharacterMovementComponent::GetPredictionData_Client() const +{ + // Should only be called on client or listen server (for remote clients) in network games + check(CharacterOwner != NULL); + checkSlow(CharacterOwner->GetLocalRole() < ROLE_Authority || (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && GetNetMode() == NM_ListenServer)); + checkSlow(GetNetMode() == NM_Client || GetNetMode() == NM_ListenServer); + + if (!ClientPredictionData) + { + UVRCharacterMovementComponent* MutableThis = const_cast(this); + MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_VRCharacter(*this); + } + + return ClientPredictionData; +} + +FNetworkPredictionData_Server* UVRCharacterMovementComponent::GetPredictionData_Server() const +{ + // Should only be called on server in network games + check(CharacterOwner != NULL); + check(CharacterOwner->GetLocalRole() == ROLE_Authority); + checkSlow(GetNetMode() < NM_Client); + + if (!ServerPredictionData) + { + UVRCharacterMovementComponent* MutableThis = const_cast(this); + MutableThis->ServerPredictionData = new FNetworkPredictionData_Server_VRCharacter(*this); + } + + return ServerPredictionData; +} + +void FSavedMove_VRCharacter::SetInitialPosition(ACharacter* C) +{ + + // See if we can get the VR capsule location + if (AVRCharacter * VRC = Cast(C)) + { + UVRCharacterMovementComponent * CharMove = Cast(VRC->GetCharacterMovement()); + if (VRC->VRRootReference) + { + VRCapsuleLocation = VRC->VRRootReference->curCameraLoc; + VRCapsuleRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRC->VRRootReference->curCameraRot); + + // Don't reset this unless its zero, after that its from a movement merge + if (LFDiff.IsZero()) + { + LFDiff = VRC->VRRootReference->DifferenceFromLastFrame; + } + } + else + { + VRCapsuleLocation = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + LFDiff = FVector::ZeroVector; + } + } + + FSavedMove_VRBaseCharacter::SetInitialPosition(C); +} + +void FSavedMove_VRCharacter::PrepMoveFor(ACharacter* Character) +{ + UVRCharacterMovementComponent * CharMove = Cast(Character->GetCharacterMovement()); + + // Set capsule location prior to testing movement + // I am overriding the replicated value here when movement is made on purpose + if (CharMove && CharMove->VRRootCapsule) + { + CharMove->VRRootCapsule->curCameraLoc = this->VRCapsuleLocation; + CharMove->VRRootCapsule->curCameraRot = this->VRCapsuleRotation;//FRotator(0.0f, FRotator::DecompressAxisFromByte(CapsuleYaw), 0.0f); + CharMove->VRRootCapsule->DifferenceFromLastFrame = LFDiff;// FVector(LFDiff.X, LFDiff.Y, 0.0f); + CharMove->AdditionalVRInputVector = CharMove->VRRootCapsule->DifferenceFromLastFrame; + + if (AVRBaseCharacter * BaseChar = Cast(CharMove->GetCharacterOwner())) + { + if (BaseChar->VRReplicateCapsuleHeight && this->CapsuleHeight > 0.0f && !FMath::IsNearlyEqual(this->CapsuleHeight, CharMove->VRRootCapsule->GetUnscaledCapsuleHalfHeight())) + { + BaseChar->SetCharacterHalfHeightVR(CapsuleHeight, false); + //CharMove->VRRootCapsule->SetCapsuleHalfHeight(this->LFDiff.Z, false); + } + } + + CharMove->VRRootCapsule->StoredCameraRotOffset = CharMove->VRRootCapsule->curCameraRot; + CharMove->VRRootCapsule->GenerateOffsetToWorld(false, false); + } + + FSavedMove_VRBaseCharacter::PrepMoveFor(Character); +} + + +void UVRCharacterMovementComponent::RegenerateOffset() +{ + if(VRRootCapsule) + VRRootCapsule->GenerateOffsetToWorld(); +} + +void UVRCharacterMovementComponent::ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) +{ + QUICK_SCOPE_CYCLE_COUNTER(VRCharacterMovementServerMove_PerformMovement); + //SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementServerMove); + //CSV_SCOPED_TIMING_STAT(CharacterMovement, CharacterMovementServerMove); + + if (!HasValidData() || !IsActive()) + { + return; + } + + bool bAutoAcceptPacket = false; + + FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character(); + check(ServerData); + + // Convert to our stored move data array + const FVRCharacterNetworkMoveData* MoveDataVR = (const FVRCharacterNetworkMoveData*)&MoveData; + + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + return; + } + else if (bJustUnseated && MoveDataVR->ReplicatedMovementMode != EVRConjoinedMovementModes::C_VRMOVE_Seated) + { + // If the client isn't still registered as seated then we accept this first change of movement and go + ServerData->CurrentClientTimeStamp = MoveData.TimeStamp; + bAutoAcceptPacket = true; + bJustUnseated = false; + } + + const float ClientTimeStamp = MoveData.TimeStamp; + FVector ClientAccel = MoveData.Acceleration; + + static const auto CVarNetUseBaseRelativeAcceleration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetUseBaseRelativeAcceleration")); + // Convert the move's acceleration to worldspace if necessary + if (CVarNetUseBaseRelativeAcceleration->GetInt() && MovementBaseUtility::IsDynamicBase(MoveData.MovementBase)) + { + MovementBaseUtility::TransformDirectionToWorld(MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.Acceleration, ClientAccel); + } + + const uint8 ClientMoveFlags = MoveData.CompressedMoveFlags; + const FRotator ClientControlRotation = MoveData.ControlRotation; + + if (!bAutoAcceptPacket && !VerifyClientTimeStamp(ClientTimeStamp, *ServerData)) + { + const float ServerTimeStamp = ServerData->CurrentClientTimeStamp; + // This is more severe if the timestamp has a large discrepancy and hasn't been recently reset. + static const auto CVarNetServerMoveTimestampExpiredWarningThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.NetServerMoveTimestampExpiredWarningThreshold")); + if (ServerTimeStamp > 1.0f && FMath::Abs(ServerTimeStamp - ClientTimeStamp) > CVarNetServerMoveTimestampExpiredWarningThreshold->GetFloat()) + { + UE_LOG(LogNetPlayerMovement, Warning, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + else + { + UE_LOG(LogNetPlayerMovement, Log, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + return; + } + + // Scope these, they nest with Outer references so it should work fine, this keeps the update rotation and move autonomous from double updating the char + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + bool bServerReadyForClient = true; + APlayerController* PC = Cast(CharacterOwner->GetController()); + if (PC) + { + bServerReadyForClient = PC->NotifyServerReceivedClientData(CharacterOwner, ClientTimeStamp); + if (!bServerReadyForClient) + { + ClientAccel = FVector::ZeroVector; + } + } + + const UWorld* MyWorld = GetWorld(); + /*const*/ float DeltaTime = 0.0f; + + // If we are auto accepting this packet, then accept it and use the maximum delta time as we don't know how long it has actually been since + // The last valid update + if (bAutoAcceptPacket) + { + DeltaTime = ServerData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld); + } + else + { + DeltaTime = ServerData->GetServerMoveDeltaTime(ClientTimeStamp, CharacterOwner->GetActorTimeDilation(*MyWorld)); + } + + if (DeltaTime > 0.f) + { + ServerData->CurrentClientTimeStamp = ClientTimeStamp; + ServerData->ServerAccumulatedClientTimeStamp += DeltaTime; + ServerData->ServerTimeStamp = MyWorld->GetTimeSeconds(); + ServerData->ServerTimeStampLastServerMove = ServerData->ServerTimeStamp; + + if (bUseClientControlRotation) + { + if (AController* CharacterController = Cast(CharacterOwner->GetController())) + { + CharacterController->SetControlRotation(ClientControlRotation); + } + } + + if (!bServerReadyForClient) + { + return; + } + + // Perform actual movement + if ((MyWorld->GetWorldSettings()->GetPauserPlayerState() == NULL)) + { + if (PC) + { + PC->UpdateRotation(DeltaTime); + } + + if (!MoveDataVR->ConditionalMoveReps.RequestedVelocity.IsZero()) + { + RequestedVelocity = MoveDataVR->ConditionalMoveReps.RequestedVelocity; + bHasRequestedVelocity = true; + } + + CustomVRInputVector = MoveDataVR->ConditionalMoveReps.CustomVRInputVector; + MoveActionArray = MoveDataVR->ConditionalMoveReps.MoveActionArray; + VRReplicatedMovementMode = MoveDataVR->ReplicatedMovementMode; + + // Set capsule location prior to testing movement + // I am overriding the replicated value here when movement is made on purpose + if (VRRootCapsule) + { + VRRootCapsule->curCameraLoc = MoveDataVR->VRCapsuleLocation; + VRRootCapsule->curCameraRot = FRotator(0.0f, FRotator::DecompressAxisFromShort(MoveDataVR->VRCapsuleRotation), 0.0f); + VRRootCapsule->DifferenceFromLastFrame = MoveDataVR->LFDiff;//FVector(MoveDataVR->LFDiff.X, MoveDataVR->LFDiff.Y, 0.0f); + AdditionalVRInputVector = VRRootCapsule->DifferenceFromLastFrame; + + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->VRReplicateCapsuleHeight && MoveDataVR->CapsuleHeight > 0.0f && !FMath::IsNearlyEqual(MoveDataVR->CapsuleHeight, VRRootCapsule->GetUnscaledCapsuleHalfHeight())) + { + BaseVRCharacterOwner->SetCharacterHalfHeightVR(MoveDataVR->CapsuleHeight, false); + // BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = LFDiff.Z; + //VRRootCapsule->SetCapsuleHalfHeight(LFDiff.Z, false); + } + } + + VRRootCapsule->StoredCameraRotOffset = VRRootCapsule->curCameraRot; + VRRootCapsule->GenerateOffsetToWorld(false, false); + } + + MoveAutonomous(ClientTimeStamp, DeltaTime, ClientMoveFlags, ClientAccel); + bHasRequestedVelocity = false; + } + + + UE_CLOG(CharacterOwner && UpdatedComponent, LogNetPlayerMovement, VeryVerbose, TEXT("ServerMove Time %f Acceleration %s Velocity %s Position %s Rotation %s GravityDirection %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d)"), + ClientTimeStamp, *ClientAccel.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), *GetGravityDirection().ToCompactString(), DeltaTime, *GetMovementName(), + *GetNameSafe(GetMovementBase()), *CharacterOwner->GetBasedMovement().BoneName.ToString(), MovementBaseUtility::IsDynamicBase(GetMovementBase()) ? 1 : 0); + } + + // #TODO: Handle this better at some point? Client also denies it later on during correction (ApplyNetworkMovementMode in base movement) + // Pre handling the errors, lets avoid rolling back to/from custom movement modes, they tend to be scripted and this can screw things up + const uint8 CurrentPackedMovementMode = PackNetworkMovementMode(); + if (CurrentPackedMovementMode != MoveData.MovementMode) + { + TEnumAsByte NetMovementMode(MOVE_None); + TEnumAsByte NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(MoveData.MovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + SetMovementMode(NetMovementMode, NetCustomMode); + } + } + + // Validate move only after old and first dual portion, after all moves are completed. + if (MoveData.NetworkMoveType == FCharacterNetworkMoveData::ENetworkMoveType::NewMove) + { + ServerMoveHandleClientErrorVR(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, ClientControlRotation.Yaw, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + //ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + } +} + +/*void UVRCharacterMovementComponent::CallServerMove +( + const class FSavedMove_Character* NewCMove, + const class FSavedMove_Character* OldCMove + ) +{ + // This is technically "safe", I know for sure that I am using my own FSavedMove + // I would have like to not override any of this, but I need a lot more specific information about the pawn + // So just setting flags in the FSaved Move doesn't cut it + // I could see a problem if someone overrides this override though + const FSavedMove_VRCharacter * NewMove = (const FSavedMove_VRCharacter *)NewCMove; + const FSavedMove_VRCharacter * OldMove = (const FSavedMove_VRCharacter *)OldCMove; + + check(NewMove != nullptr); + //uint32 ClientYawPitchINT = 0; + //uint8 ClientRollBYTE = 0; + //NewMove->GetPackedAngles(ClientYawPitchINT, ClientRollBYTE); + const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(NewMove->VRCapsuleRotation.Yaw); + const uint16 ClientYawShort = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw); + + // Determine if we send absolute or relative location + UPrimitiveComponent* ClientMovementBase = NewMove->EndBase.Get(); + const FName ClientBaseBone = NewMove->EndBoneName; + const FVector SendLocation = MovementBaseUtility::UseRelativeLocation(ClientMovementBase) ? NewMove->SavedRelativeLocation : NewMove->SavedLocation; + + // send old move if it exists + if (OldMove) + { + //const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(OldMove->VRCapsuleRotation.Yaw); + ServerMoveVROld(OldMove->TimeStamp, OldMove->Acceleration, OldMove->GetCompressedFlags(), OldMove->ConditionalValues); + } + + // Pass these in here, don't pass in to old move, it will receive the new move values in dual operations + // Will automatically not replicate them if movement base is nullptr (1 bit cost to check this) + FVRConditionalMoveRep2 NewMoveConds; + NewMoveConds.ClientMovementBase = ClientMovementBase; + NewMoveConds.ClientBaseBoneName = ClientBaseBone; + + if (CharacterOwner && (CharacterOwner->bUseControllerRotationRoll || CharacterOwner->bUseControllerRotationPitch)) + { + NewMoveConds.ClientPitch = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Pitch); + NewMoveConds.ClientRoll = FRotator::CompressAxisToByte(NewMove->SavedControlRotation.Roll); + } + + NewMoveConds.ClientYaw = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw); + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (const FSavedMove_Character* const PendingMove = ClientData->PendingMove.Get()) + { + // This should send same as the uint16 because it uses a packedINT send by default for shorts + //uint32 OldClientYawPitchINT = 0; + //uint8 OldClientRollBYTE = 0; + //ClientData->PendingMove->GetPackedAngles(OldClientYawPitchINT, OldClientRollBYTE); + + uint32 cPitch = 0; + if (CharacterOwner && (CharacterOwner->bUseControllerRotationPitch)) + cPitch = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Pitch); + + uint32 cYaw = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw); + + // Switch the order of pitch and yaw to place Yaw in smallest value, this will cut down on rep cost since normally pitch is zero'd out in VR + uint32 OldClientYawPitchINT = (cPitch << 16) | (cYaw); + + FSavedMove_VRCharacter* oldMove = (FSavedMove_VRCharacter*)ClientData->PendingMove.Get(); + const uint16 OldCapsuleYawShort = FRotator::CompressAxisToShort(oldMove->VRCapsuleRotation.Yaw); + //const uint16 OldClientYawShort = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw); + + + // If we delayed a move without root motion, and our new move has root motion, send these through a special function, so the server knows how to process them. + if ((PendingMove->RootMotionMontage == NULL) && (NewMove->RootMotionMontage != NULL)) + { + // send two moves simultaneously + ServerMoveVRDualHybridRootMotion + ( + PendingMove->TimeStamp, + PendingMove->Acceleration, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else // Not Hybrid root motion rpc + { + // send two moves simultaneously + if (oldMove->Acceleration.IsZero() && NewMove->Acceleration.IsZero()) + { + ServerMoveVRDualExLight + ( + PendingMove->TimeStamp, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else + { + ServerMoveVRDual + ( + PendingMove->TimeStamp, + PendingMove->Acceleration, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + } + } + else + { + + if (NewMove->Acceleration.IsZero()) + { + ServerMoveVRExLight + ( + NewMove->TimeStamp, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else + { + ServerMoveVR + ( + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + } + + MarkForClientCameraUpdate(); +}*/ + +bool UVRCharacterMovementComponent::ShouldCheckForValidLandingSpot(float DeltaTime, const FVector& Delta, const FHitResult& Hit) const +{ + // See if we hit an edge of a surface on the lower portion of the capsule. + // In this case the normal will not equal the impact normal, and a downward sweep may find a walkable surface on top of the edge. + if (Hit.Normal.Z > UE_KINDA_SMALL_NUMBER && !Hit.Normal.Equals(Hit.ImpactNormal)) + { + FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + if (VRRootCapsule) + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + if (IsWithinEdgeTolerance(PawnLocation, Hit.ImpactPoint, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius())) + { + return true; + } + } + + return false; +} + +void UVRCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy))) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + if (!UpdatedComponent->IsQueryCollisionEnabled()) + { + SetMovementMode(MOVE_Walking); + return; + } + + devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + bJustTeleported = false; + bool bCheckedFall = false; + bool bTriedLedgeMove = false; + float remainingTime = deltaTime; + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + // Perform the move + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy))) + { + Iterations++; + bJustTeleported = false; + const float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + // Save current values + UPrimitiveComponent * const OldBase = GetMovementBase(); + const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector; + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + + // Used for ledge check + FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation; + + const FFindFloorResult OldFloor = CurrentFloor; + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + const FVector OldVelocity = Velocity; + Acceleration = FVector::VectorPlaneProject(Acceleration, -GetGravityDirection()); + + // Apply acceleration + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + CalcVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration()); + devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + } + + ApplyRootMotionToVelocity(timeTick); + ApplyVRMotionToVelocity(deltaTime);//timeTick); + + devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + if (IsFalling()) + { + // Root motion could have put us into Falling. + // No movement has taken place this movement tick so we pass on full time/past iteration count + StartNewPhysics(remainingTime + timeTick, Iterations - 1); + return; + } + + // Compute move parameters + const FVector MoveVelocity = Velocity; + + const FVector Delta = timeTick * MoveVelocity; + + const bool bZeroDelta = Delta.IsNearlyZero(); + FStepDownResult StepDownResult; + + if (bZeroDelta) + { + remainingTime = 0.f; + // TODO: Bugged currently + /*if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride) + { + + FHitResult HitRes; + FCollisionQueryParams Params("RelativeMovementSweep", false, GetOwner()); + FCollisionResponseParams ResponseParam; + + VRRootCapsule->InitSweepCollisionParams(Params, ResponseParam); + Params.bFindInitialOverlaps = true; + bool bWasBlockingHit = false; + + bWasBlockingHit = GetWorld()->SweepSingleByChannel(HitRes, VRRootCapsule->OffsetComponentToWorld.GetLocation(), VRRootCapsule->OffsetComponentToWorld.GetLocation() + VRRootCapsule->DifferenceFromLastFrame, FQuat(0.0f, 0.0f, 0.0f, 1.0f), VRRootCapsule->GetCollisionObjectType(), VRRootCapsule->GetCollisionShape(), Params, ResponseParam); + + const FVector GravDir(0.f, 0.f, -1.f); + if (CanStepUp(HitRes) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == HitRes.GetActor())) + StepUp(GravDir,VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), HitRes, &StepDownResult); + }*/ + } + else + { + // try to move forward + MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult); + + if (IsFalling()) + { + // pawn decided to jump up + const float DesiredDist = Delta.Size(); + if (DesiredDist > UE_KINDA_SMALL_NUMBER) + { + const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D(); + remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist)); + } + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + else if (IsSwimming()) //just entered water + { + RestorePreAdditiveVRMotionVelocity(); + StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + } + + // Update floor. + // StepUp might have already done it for us. + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL); + } + + // check for ledges here + const bool bCheckLedges = !CanWalkOffLedges(); + if (bCheckLedges && !CurrentFloor.IsWalkableFloor()) + { + // calculate possible alternate movement + const FVector GravDir = GetGravityDirection(); + const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldCapsuleLocation, Delta, GravDir); + if (!NewDelta.IsZero()) + { + // first revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false); + + // avoid repeated ledge moves if the first one fails + bTriedLedgeMove = true; + + // Try new movement direction + Velocity = NewDelta / timeTick; + remainingTime += timeTick; + RestorePreAdditiveVRMotionVelocity(); + continue; + } + else + { + // see if it is OK to jump + // @todo collision : only thing that can be problem is that oldbase has world collision on + /*bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + RestorePreAdditiveVRMotionVelocity(); + return; + }*/ + bCheckedFall = true; + + // revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true); + remainingTime = 0.f; + RestorePreAdditiveVRMotionVelocity(); + break; + } + } + else + { + // Validate the floor check + if (CurrentFloor.IsWalkableFloor()) + { + if (ShouldCatchAir(OldFloor, CurrentFloor)) + { + RestorePreAdditiveVRMotionVelocity(); + HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick); + if (IsMovingOnGround()) + { + // If still walking, then fall. If not, assume the user set a different mode they want to keep. + StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation); + } + return; + } + + AdjustFloorHeight(); + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + RotateGravityToWorld(FVector(0.f, 0.f, MAX_FLOOR_DIST)); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + // check if just entered water + if (IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartSwimmingVR(OldCapsuleLocation, Velocity, timeTick, remainingTime, Iterations); + return; + } + + // See if we need to start falling. + if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating) + { + const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + bCheckedFall = true; + } + + if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, timeTick); + } + } + + + // Allow overlap events and such to change physics state and velocity + if (IsMovingOnGround()) + { + // Make velocity reflect actual move + + if (!bJustTeleported && timeTick >= MIN_TICK_TIME) + { + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes? + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick); + MaintainHorizontalGroundVelocity(); + } + + RestorePreAdditiveVRMotionVelocity(); + } + } + + // If we didn't move at all this iteration then abort (since future iterations will also be stuck). + if (UpdatedComponent->GetComponentLocation() == OldLocation) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime = 0.f; + break; + } + } + + if (IsMovingOnGround()) + { + MaintainHorizontalGroundVelocity(); + } +} + +void UVRCharacterMovementComponent::CapsuleTouched(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if (!bEnablePhysicsInteraction) + { + return; + } + + if (OtherComp != NULL && OtherComp->IsAnySimulatingPhysics()) + { + /*const*/FVector OtherLoc = OtherComp->GetComponentLocation(); + if (UVRRootComponent * rCap = Cast(OtherComp)) + { + OtherLoc = rCap->OffsetComponentToWorld.GetLocation(); + } + + const FVector Loc = VRRootCapsule->OffsetComponentToWorld.GetLocation();//UpdatedComponent->GetComponentLocation(); + FVector ImpulseDir = FVector(OtherLoc.X - Loc.X, OtherLoc.Y - Loc.Y, 0.25f).GetSafeNormal(); + ImpulseDir = (ImpulseDir + Velocity.GetSafeNormal2D()) * 0.5f; + ImpulseDir.Normalize(); + + FName BoneName = NAME_None; + if (OtherBodyIndex != INDEX_NONE) + { + BoneName = ((USkinnedMeshComponent*)OtherComp)->GetBoneName(OtherBodyIndex); + } + + float TouchForceFactorModified = TouchForceFactor; + + if (bTouchForceScaledToMass) + { + FBodyInstance* BI = OtherComp->GetBodyInstance(BoneName); + TouchForceFactorModified *= BI ? BI->GetBodyMass() : 1.0f; + } + + float ImpulseStrength = FMath::Clamp(Velocity.Size2D() * TouchForceFactorModified, + MinTouchForce > 0.0f ? MinTouchForce : -FLT_MAX, + MaxTouchForce > 0.0f ? MaxTouchForce : FLT_MAX); + + FVector Impulse = ImpulseDir * ImpulseStrength; + + OtherComp->AddImpulse(Impulse, BoneName); + } +} + +void UVRCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) +{ + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementReplicateMoveToServer); + check(CharacterOwner != NULL); + + // Can only start sending moves if our controllers are synced up over the network, otherwise we flood the reliable buffer. + APlayerController* PC = Cast(CharacterOwner->GetController()); + if (PC && PC->AcknowledgedPawn != CharacterOwner) + { + return; + } + + // Bail out if our character's controller doesn't have a Player. This may be the case when the local player + // has switched to another controller, such as a debug camera controller. + if (PC && PC->Player == nullptr) + { + return; + } + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (!ClientData) + { + return; + } + + // Update our delta time for physics simulation. + DeltaTime = ClientData->UpdateTimeStampAndDeltaTime(DeltaTime, *CharacterOwner, *this); + + // Find the oldest (unacknowledged) important move (OldMove). + // Don't include the last move because it may be combined with the next new move. + // A saved move is interesting if it differs significantly from the last acknowledged move + FSavedMovePtr OldMove = NULL; + if (ClientData->LastAckedMove.IsValid()) + { + for (int32 i = 0; i < ClientData->SavedMoves.Num() - 1; i++) + { + const FSavedMovePtr& CurrentMove = ClientData->SavedMoves[i]; + if (CurrentMove->IsImportantMove(ClientData->LastAckedMove)) + { + OldMove = CurrentMove; + break; + } + } + } + + // Get a SavedMove object to store the movement in. + FSavedMovePtr NewMovePtr = ClientData->CreateSavedMove(); + FSavedMove_Character* const NewMove = NewMovePtr.Get(); + if (NewMove == nullptr) + { + return; + } + + NewMove->SetMoveFor(CharacterOwner, DeltaTime, NewAcceleration, *ClientData); + const UWorld* MyWorld = GetWorld(); + // Causing really bad crash when using vr offset location, rather remove for now than have it merge move improperly. + + // see if the two moves could be combined + // do not combine moves which have different TimeStamps (before and after reset). + if (const FSavedMove_Character* PendingMove = ClientData->PendingMove.Get()) + { + if (bAllowMovementMerging && PendingMove->CanCombineWith(NewMovePtr, CharacterOwner, ClientData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld))) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementComponent_CombineNetMove); + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCombineNetMove); + + // Only combine and move back to the start location if we don't move back in to a spot that would make us collide with something new. + /*const */FVector OldStartLocation = PendingMove->GetRevertedLocation(); + + // Modifying the location to account for capsule loc + FVector OverlapLocation = OldStartLocation; + if (VRRootCapsule) + OverlapLocation += VRRootCapsule->OffsetComponentToWorld.GetLocation() - VRRootCapsule->GetComponentLocation(); + + const bool bAttachedToObject = (NewMovePtr->StartAttachParent != nullptr); + if (bAttachedToObject || !OverlapTest(OverlapLocation, PendingMove->StartRotation.Quaternion(), UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CharacterOwner)) + { + // Avoid updating Mesh bones to physics during the teleport back, since PerformMovement() will update it right away anyway below. + // Note: this must be before the FScopedMovementUpdate below, since that scope is what actually moves the character and mesh. + //AVRBaseCharacter * BaseCharacter = Cast(CharacterOwner); + + FScopedMeshBoneUpdateOverrideVR ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones); + + // Accumulate multiple transform updates until scope ends. + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates); + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("CombineMove: add delta %f + %f and revert from %f %f to %f %f"), DeltaTime, ClientData->PendingMove->DeltaTime, UpdatedComponent->GetComponentLocation().X, UpdatedComponent->GetComponentLocation().Y, /*OldStartLocation.X*/OverlapLocation.X, /*OldStartLocation.Y*/OverlapLocation.Y); + + NewMove->CombineWith(PendingMove, CharacterOwner, PC, OldStartLocation); + + // Update the input vector to the new values!! We were always forgetting to do this + FSavedMove_VRBaseCharacter * BaseCharMove = ((FSavedMove_VRBaseCharacter*)NewMove); + AdditionalVRInputVector = BaseCharMove->LFDiff;// FVector(BaseCharMove->LFDiff.X, BaseCharMove->LFDiff.Y, 0.0f); + + /************************/ + if (PC) + { + // We reverted position to that at the start of the pending move (above), however some code paths expect rotation to be set correctly + // before character movement occurs (via FaceRotation), so try that now. The bOrientRotationToMovement path happens later as part of PerformMovement() and PhysicsRotation(). + CharacterOwner->FaceRotation(PC->GetControlRotation(), NewMove->DeltaTime); + } + + SaveBaseLocation(); + NewMove->SetInitialPosition(CharacterOwner); + + // Remove pending move from move list. It would have to be the last move on the list. + if (ClientData->SavedMoves.Num() > 0 && ClientData->SavedMoves.Last() == ClientData->PendingMove) + { + const bool bAllowShrinking = false; + ClientData->SavedMoves.Pop(bAllowShrinking); + } + ClientData->FreeMove(ClientData->PendingMove); + ClientData->PendingMove = nullptr; + PendingMove = nullptr; // Avoid dangling reference, it's deleted above. + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [would collide at start location]")); + } + } + /*else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [not allowed by CanCombineWith()]")); + }*/ + } + + // Acceleration should match what we send to the server, plus any other restrictions the server also enforces (see MoveAutonomous). + Acceleration = NewMove->Acceleration.GetClampedToMaxSize(GetMaxAcceleration()); + AnalogInputModifier = ComputeAnalogInputModifier(); // recompute since acceleration may have changed. + + // Perform the move locally + CharacterOwner->ClientRootMotionParams.Clear(); + CharacterOwner->SavedRootMotion.Clear(); + PerformMovement(NewMove->DeltaTime); + + NewMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record); + + // Add NewMove to the list + if (CharacterOwner->IsReplicatingMovement()) + { + check(NewMove == NewMovePtr.Get()); + ClientData->SavedMoves.Push(NewMovePtr); + + static const auto CVarNetEnableMoveCombining = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableMoveCombining")); + const bool bCanDelayMove = (CVarNetEnableMoveCombining->GetInt() != 0) && CanDelaySendingMove(NewMovePtr); + + if (bCanDelayMove && ClientData->PendingMove.IsValid() == false) + { + // Decide whether to hold off on move + const float NetMoveDeltaSeconds = FMath::Clamp(GetClientNetSendDeltaTime(PC, ClientData, NewMovePtr), 1.f / 120.f, 1.f / 5.f); + const float SecondsSinceLastMoveSent = MyWorld->GetRealTimeSeconds() - ClientData->ClientUpdateRealTime; + + if (SecondsSinceLastMoveSent < NetMoveDeltaSeconds) + { + // Delay sending this move. + ClientData->PendingMove = NewMovePtr; + return; + } + } + + ClientData->ClientUpdateRealTime = MyWorld->GetRealTimeSeconds(); + + UE_CLOG(CharacterOwner&& UpdatedComponent, LogVRCharacterMovement, VeryVerbose, TEXT("ClientMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d) DualMove? %d"), + NewMove->TimeStamp, *NewMove->Acceleration.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), NewMove->DeltaTime, *GetMovementName(), + *GetNameSafe(NewMove->EndBase.Get()), *NewMove->EndBoneName.ToString(), MovementBaseUtility::IsDynamicBase(NewMove->EndBase.Get()) ? 1 : 0, ClientData->PendingMove.IsValid() ? 1 : 0); + + bool bSendServerMove = true; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + // Testing options: Simulated packet loss to server + const float TimeSinceLossStart = (MyWorld->RealTimeSeconds - ClientData->DebugForcedPacketLossTimerStart); + + static const auto CVarNetForceClientServerMoveLossDuration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossDuration")); + static const auto CVarNetForceClientServerMoveLossPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossPercent")); + if (ClientData->DebugForcedPacketLossTimerStart > 0.f && (TimeSinceLossStart < CVarNetForceClientServerMoveLossDuration->GetFloat())) + { + bSendServerMove = false; + UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat() - TimeSinceLossStart); + } + else if (CVarNetForceClientServerMoveLossPercent->GetFloat() != 0.f && (RandomStream.FRand() < CVarNetForceClientServerMoveLossPercent->GetFloat())) + { + bSendServerMove = false; + ClientData->DebugForcedPacketLossTimerStart = (CVarNetForceClientServerMoveLossDuration->GetFloat() > 0) ? MyWorld->RealTimeSeconds : 0.0f; + UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat()); + } + else + { + ClientData->DebugForcedPacketLossTimerStart = 0.f; + } +#endif + + // Send move to server if this character is replicating movement + if (bSendServerMove) + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCallServerMove); + if (ShouldUsePackedMovementRPCs()) + { + CallServerMovePacked(NewMove, ClientData->PendingMove.Get(), OldMove.Get()); + } + /*else + { + CallServerMove(NewMove, OldMove.Get()); + }*/ + } + } + + ClientData->PendingMove = NULL; +} + +/* +* +* +* END TEST AREA +* +* +*/ + +UVRCharacterMovementComponent::UVRCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PostPhysicsTickFunction.bCanEverTick = true; + PostPhysicsTickFunction.bStartWithTickEnabled = false; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + VRRootCapsule = NULL; + //VRCameraCollider = NULL; + // 0.1f is low slide and still impacts surfaces well + // This variable is a bit of a hack, it reduces the movement of the pawn in the direction of relative movement + //WallRepulsionMultiplier = 0.01f; + bUseClientControlRotation = false; + bAllowMovementMerging = true; + bRunClientCorrectionToHMD = false; + bRequestedMoveUseAcceleration = false; +} + +void UVRCharacterMovementComponent::OnRegister() +{ + Super::OnRegister(); + + //#TODO: double check that this works, they enforce linear usually + // Force exponential smoothing for replays. + const UWorld* MyWorld = GetWorld(); + const bool bIsReplay = (MyWorld && MyWorld->IsPlayingReplay()); + + if (bIsReplay) + { + //NetworkSmoothingMode = ENetworkSmoothingMode::Linear; + NetworkSmoothingMode = ENetworkSmoothingMode::Exponential; + } +} + + +void UVRCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!HasValidData()) + { + return; + } + + if (CharacterOwner && CharacterOwner->IsLocallyControlled()) + { + // Root capsule is now throwing out the difference itself, I use the difference for multiplayer sends + if (VRRootCapsule) + { + AdditionalVRInputVector = VRRootCapsule->DifferenceFromLastFrame; + } + else + { + AdditionalVRInputVector = FVector::ZeroVector; + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + +// No support for crouching code yet +bool UVRCharacterMovementComponent::CanCrouch() +{ + return false; +} + + +void UVRCharacterMovementComponent::ApplyRepulsionForce(float DeltaSeconds) +{ + if (UpdatedPrimitive && RepulsionForce > 0.0f && CharacterOwner != nullptr) + { + const TArray& Overlaps = UpdatedPrimitive->GetOverlapInfos(); + if (Overlaps.Num() > 0) + { + FCollisionQueryParams QueryParams; + QueryParams.bReturnFaceIndex = false; + QueryParams.bReturnPhysicalMaterial = false; + + float CapsuleRadius = 0.f; + float CapsuleHalfHeight = 0.f; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(CapsuleRadius, CapsuleHalfHeight); + const float RepulsionForceRadius = CapsuleRadius * 1.2f; + const float StopBodyDistance = 2.5f; + FVector MyLocation; + + if (VRRootCapsule) + MyLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + MyLocation = UpdatedPrimitive->GetComponentLocation(); + + for (int32 i = 0; i < Overlaps.Num(); i++) + { + const FOverlapInfo& Overlap = Overlaps[i]; + + UPrimitiveComponent* OverlapComp = Overlap.OverlapInfo.Component.Get(); + if (!OverlapComp || OverlapComp->Mobility < EComponentMobility::Movable) + { + continue; + } + + // Use the body instead of the component for cases where we have multi-body overlaps enabled + FBodyInstance* OverlapBody = nullptr; + const int32 OverlapBodyIndex = Overlap.GetBodyIndex(); + const USkeletalMeshComponent* SkelMeshForBody = (OverlapBodyIndex != INDEX_NONE) ? Cast(OverlapComp) : nullptr; + if (SkelMeshForBody != nullptr) + { + OverlapBody = SkelMeshForBody->Bodies.IsValidIndex(OverlapBodyIndex) ? SkelMeshForBody->Bodies[OverlapBodyIndex] : nullptr; + } + else + { + OverlapBody = OverlapComp->GetBodyInstance(); + } + + if (!OverlapBody) + { + UE_LOG(LogVRCharacterMovement, Warning, TEXT("%s could not find overlap body for body index %d"), *GetName(), OverlapBodyIndex); + continue; + } + + if (!OverlapBody->IsInstanceSimulatingPhysics()) + { + continue; + } + + FTransform BodyTransform = OverlapBody->GetUnrealWorldTransform(); + + FVector BodyVelocity = OverlapBody->GetUnrealWorldVelocity(); + FVector BodyLocation = BodyTransform.GetLocation(); + + // Trace to get the hit location on the capsule + FHitResult Hit; + bool bHasHit = UpdatedPrimitive->LineTraceComponent(Hit, BodyLocation, + FVector(MyLocation.X, MyLocation.Y, BodyLocation.Z), + QueryParams); + + FVector HitLoc = Hit.ImpactPoint; + bool bIsPenetrating = Hit.bStartPenetrating || Hit.PenetrationDepth > StopBodyDistance; + + // If we didn't hit the capsule, we're inside the capsule + if (!bHasHit) + { + HitLoc = BodyLocation; + bIsPenetrating = true; + } + + const float DistanceNow = (HitLoc - BodyLocation).SizeSquared2D(); + const float DistanceLater = (HitLoc - (BodyLocation + BodyVelocity * DeltaSeconds)).SizeSquared2D(); + + if (bHasHit && DistanceNow < StopBodyDistance && !bIsPenetrating) + { + OverlapBody->SetLinearVelocity(FVector(0.0f, 0.0f, 0.0f), false); + } + else if (DistanceLater <= DistanceNow || bIsPenetrating) + { + FVector ForceCenter = MyLocation; + + if (bHasHit) + { + ForceCenter.Z = HitLoc.Z; + } + else + { + ForceCenter.Z = FMath::Clamp(BodyLocation.Z, MyLocation.Z - CapsuleHalfHeight, MyLocation.Z + CapsuleHalfHeight); + } + + OverlapBody->AddRadialForceToBody(ForceCenter, RepulsionForceRadius, RepulsionForce * Mass, ERadialImpulseFalloff::RIF_Constant); + } + } + } + } +} + + +void UVRCharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + + if (UpdatedComponent) + { + // Fill the VRRootCapsule if we can + VRRootCapsule = Cast(UpdatedComponent); + + // Fill in the camera collider if we can + /*if (AVRCharacter * vrOwner = Cast(this->GetOwner())) + { + VRCameraCollider = vrOwner->VRCameraCollider; + }*/ + + // Stop the tick forcing + UpdatedComponent->PrimaryComponentTick.RemovePrerequisite(this, PrimaryComponentTick); + + // Start forcing the root to tick before this, the actor tick will still tick after the movement component + // We want the root component to tick first because it is setting its offset location based off of tick + this->PrimaryComponentTick.AddPrerequisite(UpdatedComponent, UpdatedComponent->PrimaryComponentTick); + } +} + +FORCEINLINE_DEBUGGABLE bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport) +{ + return SafeMoveUpdatedComponent(Delta, NewRotation.Quaternion(), bSweep, OutHit, Teleport); +} + +bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport) +{ + if (UpdatedComponent == NULL) + { + OutHit.Reset(1.f); + return false; + } + + bool bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &OutHit, Teleport); + + // Handle initial penetrations + if (OutHit.bStartPenetrating && UpdatedComponent) + { + const FVector RequestedAdjustment = GetPenetrationAdjustment(OutHit); + if (ResolvePenetration(RequestedAdjustment, OutHit, NewRotation)) + { + FHitResult TempHit; + // Retry original move + bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &TempHit, Teleport); + + // Remove start penetrating if this is a clean move, otherwise use the last moves hit as the result so step up actually attempts to work. + if (TempHit.bStartPenetrating) + OutHit = TempHit; + else + OutHit.bStartPenetrating = TempHit.bStartPenetrating; + } + } + + return bMoveResult; +} + +void UVRCharacterMovementComponent::MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult) +{ + if (!CurrentFloor.IsWalkableFloor()) + { + return; + } + + // Move along the current floor + const FVector Delta = RotateGravityToWorld(RotateWorldToGravity(InVelocity) * FVector(1.0, 1.0, 0.0)) * DeltaSeconds; + FHitResult Hit(1.f); + FVector RampVector = ComputeGroundMovementDelta(Delta, CurrentFloor.HitResult, CurrentFloor.bLineTrace); + SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit); + float LastMoveTimeSlice = DeltaSeconds; + + if (Hit.bStartPenetrating) + { + // Allow this hit to be used as an impact we can deflect off, otherwise we do nothing the rest of the update and appear to hitch. + HandleImpact(Hit); + SlideAlongSurface(Delta, 1.f, Hit.Normal, Hit, true); + + if (Hit.bStartPenetrating) + { + OnCharacterStuckInGeometry(&Hit); + } + } + else if (Hit.IsValidBlockingHit()) + { + // We impacted something (most likely another ramp, but possibly a barrier). + float PercentTimeApplied = Hit.Time; + if ((Hit.Time > 0.f) && (Hit.Normal.Z > UE_KINDA_SMALL_NUMBER) && IsWalkable(Hit)) + { + // Another walkable ramp. + const float InitialPercentRemaining = 1.f - PercentTimeApplied; + RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false); + LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice; + SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit); + + const float SecondHitPercent = Hit.Time * InitialPercentRemaining; + PercentTimeApplied = FMath::Clamp(PercentTimeApplied + SecondHitPercent, 0.f, 1.f); + } + + if (Hit.IsValidBlockingHit()) + { + if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != nullptr && Hit.HitObjectHandle == CharacterOwner->GetMovementBase()->GetOwner())) + { + // hit a barrier, try to step up + const FVector PreStepUpLocation = UpdatedComponent->GetComponentLocation(); + const FVector GravDir = GetGravityDirection(); + + // I add in the HMD difference from last frame to the step up check to enforce it stepping up + if (!StepUp(GravDir, (Delta * (1.f - PercentTimeApplied)) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, OutStepDownResult)) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString()); + HandleImpact(Hit, LastMoveTimeSlice, RampVector); + SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true); + + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("+ StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString()); + if (!bMaintainHorizontalGroundVelocity) + { + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. Only consider horizontal movement. + bJustTeleported = true; + const float StepUpTimeSlice = (1.f - PercentTimeApplied) * DeltaSeconds; + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && StepUpTimeSlice >= UE_KINDA_SMALL_NUMBER) + { + Velocity = (UpdatedComponent->GetComponentLocation() - PreStepUpLocation) / StepUpTimeSlice; + Velocity = FVector::VectorPlaneProject(Velocity, -GravDir); + } + } + } + } + else if (Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner)) + { + HandleImpact(Hit, LastMoveTimeSlice, RampVector); + SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true); + + } + } + } +} + +bool UVRCharacterMovementComponent::StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + SCOPE_CYCLE_COUNTER(STAT_CharStepUp); + + if (!CanStepUp(InHit) || MaxStepHeight <= 0.f) + { + return false; + } + + FVector OldLocation; + + if (VRRootCapsule) + OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + OldLocation = UpdatedComponent->GetComponentLocation(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + // Don't bother stepping up if top of capsule is hitting something. + const float InitialImpactZ = InHit.ImpactPoint.Z; + if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius)) + { + return false; + } + + if (GravDir.IsZero()) + { + return false; + } + + // Gravity should be a normalized direction + ensure(GravDir.IsNormalized()); + + float StepTravelUpHeight = MaxStepHeight; + float StepTravelDownHeight = StepTravelUpHeight; + const float StepSideZ = -1.f * FVector::DotProduct(InHit.ImpactNormal, GravDir);//const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir); + float PawnInitialFloorBaseZ = OldLocation.Z -PawnHalfHeight; + float PawnFloorPointZ = PawnInitialFloorBaseZ; + + if (IsMovingOnGround() && CurrentFloor.IsWalkableFloor()) + { + // Since we float a variable amount off the floor, we need to enforce max step height off the actual point of impact with the floor. + const float FloorDist = FMath::Max(0.f, CurrentFloor.GetDistanceToFloor()); + PawnInitialFloorBaseZ -= FloorDist; + StepTravelUpHeight = FMath::Max(StepTravelUpHeight - FloorDist, 0.f); + StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST*2.f); + + const bool bHitVerticalFace = !IsWithinEdgeTolerance(InHit.Location, InHit.ImpactPoint, PawnRadius); + if (!CurrentFloor.bLineTrace && !bHitVerticalFace) + { + PawnFloorPointZ = CurrentFloor.HitResult.ImpactPoint.Z; + } + else + { + // Base floor point is the base of the capsule moved down by how far we are hovering over the surface we are hitting. + PawnFloorPointZ -= CurrentFloor.FloorDist; + } + } + + // Don't step up if the impact is below us, accounting for distance from floor. + if (InitialImpactZ <= PawnInitialFloorBaseZ) + { + return false; + } + + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + /*FScopedMovementUpdate*/ FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + // step up - treat as vertical wall + FHitResult SweepUpHit(1.f); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit); + + if (SweepUpHit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + + // step fwd + FHitResult Hit(1.f); + MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + // Check result of forward movement + if (Hit.bBlockingHit) + { + if (Hit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If we hit something above us and also something ahead of us, we should notify about the upward hit as well. + // The forward hit will be handled later (in the bSteppedOver case below). + // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification. + if (SweepUpHit.bBlockingHit && Hit.bBlockingHit) + { + HandleImpact(SweepUpHit); + } + + // pawn ran into a wall + HandleImpact(Hit); + if (IsFalling()) + { + return true; + } + + // adjust and try again + const float ForwardHitTime = Hit.Time; + const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); + + if (IsFalling()) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If both the forward hit and the deflection got us nowhere, there is no point in this step up. + if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Step down + MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit); + + // If step down was initially penetrating abort the step up + if (Hit.bStartPenetrating) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + FStepDownResult StepDownResult; + if (Hit.IsValidBlockingHit()) + { + // See if this step sequence would have allowed us to travel higher than our max step height allows. + const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ; + if (DeltaZ > MaxStepHeight) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Reject unwalkable surface normals here. + if (!IsWalkable(Hit)) + { + // Reject if normal opposes movement direction + const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f; + if (bNormalTowardsMe) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Also reject if we would end up being higher than our starting location by stepping down. + // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge. + if (Hit.Location.Z > OldLocation.Z) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well. + if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius)) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Don't step up onto invalid surfaces if traveling higher. + if (DeltaZ > 0.f && !CanStepUp(Hit)) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method. + if (OutStepDownResult != NULL) + { + FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit); + + // Reject unwalkable normals if we end up higher than our initial height. + // It's fine to walk down onto an unwalkable surface, don't reject those moves. + if (Hit.Location.Z > OldLocation.Z) + { + // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare). + // In those cases we should instead abort the step up and try to slide along the stair. + if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < CharacterMovementConstants::MAX_STEP_SIDE_ZVR) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + StepDownResult.bComputedFloor = true; + + if (bAutoOrientToFloorNormal && StepDownResult.FloorResult.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal, don't blend the change, snap to it on a step up + AutoTraceAndSetCharacterToNewGravity(StepDownResult.FloorResult.HitResult, 0.0f); + } + } + } + + // Copy step down result. + if (OutStepDownResult != NULL) + { + *OutStepDownResult = StepDownResult; + } + + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. + bJustTeleported |= !bMaintainHorizontalGroundVelocity; + + return true; +} + +bool UVRCharacterMovementComponent::IsWithinEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const +{ + const FVector GravityRelativeToTestImpactPoint = RotateWorldToGravity(TestImpactPoint - CapsuleLocation); + const float DistFromCenterSq = GravityRelativeToTestImpactPoint.SizeSquared2D(); + const float ReducedRadiusSq = FMath::Square(FMath::Max(VREdgeRejectDistance + UE_KINDA_SMALL_NUMBER, CapsuleRadius - VREdgeRejectDistance)); + return DistFromCenterSq < ReducedRadiusSq; +} + +bool UVRCharacterMovementComponent::IsWithinClimbingEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const +{ + const float DistFromCenterSq = (TestImpactPoint - CapsuleLocation).SizeSquared2D(); + const float ReducedRadiusSq = FMath::Square(FMath::Max(VRClimbingEdgeRejectDistance + UE_KINDA_SMALL_NUMBER, CapsuleRadius - VRClimbingEdgeRejectDistance)); + return DistFromCenterSq < ReducedRadiusSq; +} + +bool UVRCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + SCOPE_CYCLE_COUNTER(STAT_CharStepUp); + + if (!CanStepUp(InHit) || MaxStepHeight <= 0.f) + { + return false; + } + + FVector OldLocation; + + if (VRRootCapsule) + OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + OldLocation = UpdatedComponent->GetComponentLocation(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + // Don't bother stepping up if top of capsule is hitting something. + const float InitialImpactZ = InHit.ImpactPoint.Z; + if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius)) + { + return false; + } + + // Don't step up if the impact is below us + if (InitialImpactZ <= OldLocation.Z - PawnHalfHeight) + { + return false; + } + + if (GravDir.IsZero()) + { + return false; + } + + // Gravity should be a normalized direction + ensure(GravDir.IsNormalized()); + + float StepTravelUpHeight = MaxStepHeight; + float StepTravelDownHeight = StepTravelUpHeight; + const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir); + float PawnInitialFloorBaseZ = OldLocation.Z - PawnHalfHeight; + float PawnFloorPointZ = PawnInitialFloorBaseZ; + + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + // step up - treat as vertical wall + FHitResult SweepUpHit(1.f); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit); + + if (SweepUpHit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // step fwd + FHitResult Hit(1.f); + + + // Adding in the directional difference of the last HMD movement to promote stepping up + // Probably entirely wrong as Delta is divided by movement ticks but I want the effect to be stronger anyway + // This won't effect control based movement unless stepping forward at the same time, but gives RW movement + // the extra boost to get up over a lip + // #TODO test this more, currently appears to be needed for walking, but is harmful for other modes +// if (VRRootCapsule) +// MoveUpdatedComponent(Delta + VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), PawnRotation, true, &Hit); +// else + MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + //MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + // Check result of forward movement + if (Hit.bBlockingHit) + { + if (Hit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If we hit something above us and also something ahead of us, we should notify about the upward hit as well. + // The forward hit will be handled later (in the bSteppedOver case below). + // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification. + if (SweepUpHit.bBlockingHit && Hit.bBlockingHit) + { + + HandleImpact(SweepUpHit); + } + + // pawn ran into a wall + HandleImpact(Hit); + if (IsFalling()) + { + return true; + } + + //Don't adjust for VR, it doesn't work correctly + ScopedStepUpMovement.RevertMove(); + return false; + + // adjust and try again + //const float ForwardHitTime = Hit.Time; + //const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); + + //if (IsFalling()) + //{ + // ScopedStepUpMovement.RevertMove(); + // return false; + //} + + // If both the forward hit and the deflection got us nowhere, there is no point in this step up. + //if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f) + //{ + // ScopedStepUpMovement.RevertMove(); + // return false; + //} + } + + // Step down + MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit); + + // If step down was initially penetrating abort the step up + if (Hit.bStartPenetrating) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + FStepDownResult StepDownResult; + if (Hit.IsValidBlockingHit()) + { + // See if this step sequence would have allowed us to travel higher than our max step height allows. + const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ; + if (DeltaZ > MaxStepHeight) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Reject unwalkable surface normals here. + if (!IsWalkable(Hit)) + { + // Reject if normal opposes movement direction + const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f; + if (bNormalTowardsMe) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Also reject if we would end up being higher than our starting location by stepping down. + // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge. + if (Hit.Location.Z > OldLocation.Z) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well. + if (!IsWithinClimbingEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius)) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Don't step up onto invalid surfaces if traveling higher. + if (DeltaZ > 0.f && !CanStepUp(Hit)) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method. + if (OutStepDownResult != NULL) + { + FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit); + + // Reject unwalkable normals if we end up higher than our initial height. + // It's fine to walk down onto an unwalkable surface, don't reject those moves. + if (Hit.Location.Z > OldLocation.Z) + { + // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare). + // In those cases we should instead abort the step up and try to slide along the stair. + if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < CharacterMovementConstants::MAX_STEP_SIDE_ZVR) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + StepDownResult.bComputedFloor = true; + + if (bAutoOrientToFloorNormal && StepDownResult.FloorResult.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal, don't blend the change, snap to it on a step down + AutoTraceAndSetCharacterToNewGravity(StepDownResult.FloorResult.HitResult, 0.0f); + } + } + } + + // Copy step down result. + if (OutStepDownResult != NULL) + { + *OutStepDownResult = StepDownResult; + } + + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. + bJustTeleported |= !bMaintainHorizontalGroundVelocity; + return true; +} + + +void UVRCharacterMovementComponent::UpdateBasedMovement(float DeltaSeconds) +{ + if (!HasValidData()) + { + return; + } + + const UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + if (!MovementBaseUtility::UseRelativeLocation(MovementBase)) + { + return; + } + + if (!IsValid(MovementBase) || !IsValid(MovementBase->GetOwner())) + { + SetBase(NULL); + return; + } + + // Ignore collision with bases during these movements. + TGuardValue ScopedFlagRestore(MoveComponentFlags, MoveComponentFlags | MOVECOMP_IgnoreBases); + + FQuat DeltaQuat = FQuat::Identity; + FVector DeltaPosition = FVector::ZeroVector; + + FQuat NewBaseQuat; + FVector NewBaseLocation; + if (!MovementBaseUtility::GetMovementBaseTransform(MovementBase, CharacterOwner->GetBasedMovement().BoneName, NewBaseLocation, NewBaseQuat)) + { + return; + } + + // Find change in rotation + const bool bRotationChanged = !OldBaseQuat.Equals(NewBaseQuat, 1e-8f); + if (bRotationChanged) + { + DeltaQuat = NewBaseQuat * OldBaseQuat.Inverse(); + } + + // only if base moved + if (bRotationChanged || (OldBaseLocation != NewBaseLocation)) + { + // Calculate new transform matrix of base actor (ignoring scale). + const FQuatRotationTranslationMatrix OldLocalToWorld(OldBaseQuat, OldBaseLocation); + const FQuatRotationTranslationMatrix NewLocalToWorld(NewBaseQuat, NewBaseLocation); + + FQuat FinalQuat = UpdatedComponent->GetComponentQuat(); + + if (bRotationChanged && !bIgnoreBaseRotation) + { + // Apply change in rotation and pipe through FaceRotation to maintain axis restrictions + const FQuat PawnOldQuat = UpdatedComponent->GetComponentQuat(); + const FQuat TargetQuat = DeltaQuat * FinalQuat; + FRotator TargetRotator(TargetQuat); + CharacterOwner->FaceRotation(TargetRotator, 0.f); + FinalQuat = UpdatedComponent->GetComponentQuat(); + + if (PawnOldQuat.Equals(FinalQuat, 1e-6f)) + { + // Nothing changed. This means we probably are using another rotation mechanism (bOrientToMovement etc). We should still follow the base object. + // @todo: This assumes only Yaw is used, currently a valid assumption. This is the only reason FaceRotation() is used above really, aside from being a virtual hook. + if (bOrientRotationToMovement || (bUseControllerDesiredRotation && CharacterOwner->Controller)) + { + TargetRotator.Pitch = 0.f; + TargetRotator.Roll = 0.f; + MoveUpdatedComponent(FVector::ZeroVector, TargetRotator, false); + FinalQuat = UpdatedComponent->GetComponentQuat(); + } + } + + // Pipe through ControlRotation, to affect camera. + if (CharacterOwner->Controller) + { + const FQuat PawnDeltaRotation = FinalQuat * PawnOldQuat.Inverse(); + FRotator FinalRotation = FinalQuat.Rotator(); + UpdateBasedRotation(FinalRotation, PawnDeltaRotation.Rotator()); + FinalQuat = UpdatedComponent->GetComponentQuat(); + } + } + + // We need to offset the base of the character here, not its origin, so offset by half height + float HalfHeight, Radius; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(Radius, HalfHeight); + + if (!BaseVRCharacterOwner || BaseVRCharacterOwner->bRetainRoomscale) + { + HalfHeight = 0; + } + + FVector const BaseOffset(0.0f, 0.0f, HalfHeight); + + FVector const LocalBasePos = OldLocalToWorld.InverseTransformPosition(UpdatedComponent->GetComponentLocation() - BaseOffset); + FVector const NewWorldPos = ConstrainLocationToPlane(NewLocalToWorld.TransformPosition(LocalBasePos) + BaseOffset); + DeltaPosition = ConstrainDirectionToPlane(NewWorldPos - UpdatedComponent->GetComponentLocation()); + + // move attached actor + if (bFastAttachedMove) + { + // we're trusting no other obstacle can prevent the move here + UpdatedComponent->SetWorldLocationAndRotation(NewWorldPos, FinalQuat, false); + } + else + { + // hack - transforms between local and world space introducing slight error FIXMESTEVE - discuss with engine team: just skip the transforms if no rotation? + FVector BaseMoveDelta = NewBaseLocation - OldBaseLocation; + if (!bRotationChanged && (BaseMoveDelta.X == 0.f) && (BaseMoveDelta.Y == 0.f)) + { + DeltaPosition.X = 0.f; + DeltaPosition.Y = 0.f; + } + + FHitResult MoveOnBaseHit(1.f); + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + MoveUpdatedComponent(DeltaPosition, FinalQuat, true, &MoveOnBaseHit); + if ((UpdatedComponent->GetComponentLocation() - (OldLocation + DeltaPosition)).IsNearlyZero() == false) + { + OnUnableToFollowBaseMove(DeltaPosition, OldLocation, MoveOnBaseHit); + } + } + + if (MovementBase->IsSimulatingPhysics() && CharacterOwner->GetMesh()) + { + CharacterOwner->GetMesh()->ApplyDeltaToAllPhysicsTransforms(DeltaPosition, DeltaQuat); + } + } +} + +FVector UVRCharacterMovementComponent::GetImpartedMovementBaseVelocity() const +{ + FVector Result = FVector::ZeroVector; + + if (CharacterOwner) + { + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + if (MovementBaseUtility::IsDynamicBase(MovementBase)) + { + FVector BaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName); + + if (bImpartBaseAngularVelocity) + { + // Base position should be the bottom of the actor since I offset the capsule now + + float HalfHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); + if (!BaseVRCharacterOwner || BaseVRCharacterOwner->bRetainRoomscale) + { + HalfHeight = 0.0f; + } + + const FVector CharacterBasePosition = (UpdatedComponent->GetComponentLocation() - FVector(0.f, 0.f, HalfHeight)); + + + const FVector BaseTangentialVel = MovementBaseUtility::GetMovementBaseTangentialVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName, CharacterBasePosition); + BaseVelocity += BaseTangentialVel; + } + + if (bImpartBaseVelocityX) + { + Result.X = BaseVelocity.X; + } + if (bImpartBaseVelocityY) + { + Result.Y = BaseVelocity.Y; + } + if (bImpartBaseVelocityZ) + { + Result.Z = BaseVelocity.Z; + } + } + } + + return Result; +} + + + +void UVRCharacterMovementComponent::FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult, bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult) const +{ + SCOPE_CYCLE_COUNTER(STAT_CharFindFloor); + //UE_LOG(LogVRCharacterMovement, Warning, TEXT("Find Floor")); + // No collision, no floor... + if (!HasValidData() || !UpdatedComponent->IsQueryCollisionEnabled()) + { + OutFloorResult.Clear(); + return; + } + + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("[Role:%d] FindFloor: %s at location %s"), (int32)CharacterOwner->Role, *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString()); + check(CharacterOwner->GetCapsuleComponent()); + + FVector UseCapsuleLocation = CapsuleLocation; + if (VRRootCapsule) + UseCapsuleLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + // Increase height check slightly if walking, to prevent floor height adjustment from later invalidating the floor result. + const float HeightCheckAdjust = ((IsMovingOnGround() || IsClimbing()) ? MAX_FLOOR_DIST + UE_KINDA_SMALL_NUMBER : -MAX_FLOOR_DIST); + + float FloorSweepTraceDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust); + float FloorLineTraceDist = FloorSweepTraceDist; + bool bNeedToValidateFloor = true; + + // For reverting + FFindFloorResult LastFloor = CurrentFloor; + + // Sweep floor + if (FloorLineTraceDist > 0.f || FloorSweepTraceDist > 0.f) + { + UCharacterMovementComponent* MutableThis = const_cast((UCharacterMovementComponent*)this); + + if (bAlwaysCheckFloor || !bCanUseCachedLocation || bForceNextFloorCheck || bJustTeleported) + { + MutableThis->bForceNextFloorCheck = false; + ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult); + } + else + { + // Force floor check if base has collision disabled or if it does not block us. + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + const AActor* BaseActor = MovementBase ? MovementBase->GetOwner() : NULL; + + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + + if (MovementBase != NULL) + { + MutableThis->bForceNextFloorCheck = !MovementBase->IsQueryCollisionEnabled() + || MovementBase->GetCollisionResponseToChannel(CollisionChannel) != ECR_Block + || MovementBaseUtility::IsDynamicBase(MovementBase); + } + + const bool IsActorBasePendingKill = !IsValid(BaseActor); + + if (!bForceNextFloorCheck && !IsActorBasePendingKill && MovementBase) + { + //UE_LOG(LogVRCharacterMovement, Log, TEXT("%s SKIP check for floor"), *CharacterOwner->GetName()); + OutFloorResult = CurrentFloor; + bNeedToValidateFloor = false; + } + else + { + MutableThis->bForceNextFloorCheck = false; + ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult); + } + } + } + + // #TODO: Modify the floor compute floor distance instead? Or just run movement logic differently for the bWalkingCollisionOverride setup. + // #VR Specific - ignore floor traces that are negative, this can be caused by a failed floor check while starting in penetration + if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && OutFloorResult.bBlockingHit && OutFloorResult.FloorDist <= 0.0f) + { + + if (OutFloorResult.FloorDist <= -FMath::Max(MAX_FLOOR_DIST, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius())) + { + // This was a negative trace result, the game wants us to pull out of penetration + // But with walking collision override we don't want to do that, so check for the correct channel and throw away + // the new floor if it matches + ECollisionResponse FloorResponse; + if (OutFloorResult.HitResult.Component.IsValid()) + { + FloorResponse = OutFloorResult.HitResult.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride); + if (FloorResponse == ECR_Ignore || FloorResponse == ECR_Overlap) + OutFloorResult = LastFloor; // Move back to the last floor value, we are in penetration with a walking override + } + } + } + + // OutFloorResult.HitResult is now the result of the vertical floor check. + // See if we should try to "perch" at this location. + if (bNeedToValidateFloor && OutFloorResult.bBlockingHit && !OutFloorResult.bLineTrace) + { + const bool bCheckRadius = true; + if (ShouldComputePerchResult(OutFloorResult.HitResult, bCheckRadius)) + { + float MaxPerchFloorDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust); + if (IsMovingOnGround() || IsClimbing()) + { + MaxPerchFloorDist += FMath::Max(0.f, PerchAdditionalHeight); + } + + FFindFloorResult PerchFloorResult; + if (ComputePerchResult(GetValidPerchRadius(), OutFloorResult.HitResult, MaxPerchFloorDist, PerchFloorResult)) + { + // Don't allow the floor distance adjustment to push us up too high, or we will move beyond the perch distance and fall next time. + const float AvgFloorDist = (MIN_FLOOR_DIST + MAX_FLOOR_DIST) * 0.5f; + const float MoveUpDist = (AvgFloorDist - OutFloorResult.FloorDist); + if (MoveUpDist + PerchFloorResult.FloorDist >= MaxPerchFloorDist) + { + OutFloorResult.FloorDist = AvgFloorDist; + } + + // If the regular capsule is on an unwalkable surface but the perched one would allow us to stand, override the normal to be one that is walkable. + if (!OutFloorResult.bWalkableFloor) + { + OutFloorResult.SetFromLineTrace(PerchFloorResult.HitResult, OutFloorResult.FloorDist, FMath::Max(OutFloorResult.FloorDist, MIN_FLOOR_DIST), true); + } + } + else + { + // We had no floor (or an invalid one because it was unwalkable), and couldn't perch here, so invalidate floor (which will cause us to start falling). + OutFloorResult.bWalkableFloor = false; + } + } + } +} + +float UVRCharacterMovementComponent::ImmersionDepth() const +{ + float depth = 0.f; + + if (CharacterOwner && GetPhysicsVolume()->bWaterVolume) + { + const float CollisionHalfHeight = CharacterOwner->GetSimpleCollisionHalfHeight(); + + if ((CollisionHalfHeight == 0.f) || (Buoyancy == 0.f)) + { + depth = 1.f; + } + else + { + UBrushComponent* VolumeBrushComp = GetPhysicsVolume()->GetBrushComponent(); + FHitResult Hit(1.f); + if (VolumeBrushComp) + { + FVector TraceStart; + FVector TraceEnd; + + if (VRRootCapsule) + { + TraceStart = VRRootCapsule->OffsetComponentToWorld.GetLocation() + FVector(0.f, 0.f, CollisionHalfHeight); + TraceEnd = VRRootCapsule->OffsetComponentToWorld.GetLocation() - FVector(0.f, 0.f, CollisionHalfHeight); + } + else + { + TraceStart = UpdatedComponent->GetComponentLocation() + FVector(0.f, 0.f, CollisionHalfHeight); + TraceEnd = UpdatedComponent->GetComponentLocation() -FVector(0.f, 0.f, CollisionHalfHeight); + } + + FCollisionQueryParams NewTraceParams(CharacterMovementComponentStatics::ImmersionDepthName, true); + VolumeBrushComp->LineTraceComponent(Hit, TraceStart, TraceEnd, NewTraceParams); + } + + depth = (Hit.Time == 1.f) ? 1.f : (1.f - Hit.Time); + } + } + return depth; +} + +/////////////////////////// +// Navigation Functions +/////////////////////////// + +bool UVRCharacterMovementComponent::TryToLeaveNavWalking() +{ + SetNavWalkingPhysics(false); + + bool bCanTeleport = true; + if (CharacterOwner) + { + FVector CollisionFreeLocation; + if (VRRootCapsule) + CollisionFreeLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + CollisionFreeLocation = UpdatedComponent->GetComponentLocation(); + + // Think I need to create a custom "FindTeleportSpot" function, it is using ComponentToWorld location + bCanTeleport = GetWorld()->FindTeleportSpot(CharacterOwner, CollisionFreeLocation, UpdatedComponent->GetComponentRotation()); + if (bCanTeleport) + { + + if (VRRootCapsule) + { + // Technically the same actor but i am keepign the usage convention for clarity. + // Subtracting actor location from capsule to get difference in worldspace, then removing from collision free location + // So that it uses the correct location. + CharacterOwner->SetActorLocation(CollisionFreeLocation - (VRRootCapsule->OffsetComponentToWorld.GetLocation() - UpdatedComponent->GetComponentLocation())); + } + else + CharacterOwner->SetActorLocation(CollisionFreeLocation); + } + else + { + SetNavWalkingPhysics(true); + } + } + + bWantsToLeaveNavWalking = !bCanTeleport; + return bCanTeleport; +} + +void UVRCharacterMovementComponent::PhysFlying(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + if (bCheatFlying && Acceleration.IsZero()) + { + Velocity = FVector::ZeroVector; + } + const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction; + CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration()); + } + + ApplyRootMotionToVelocity(deltaTime); + //ApplyVRMotionToVelocity(deltaTime); + + // Manually handle the velocity setup + LastPreAdditiveVRVelocity = (AdditionalVRInputVector) / deltaTime; + bool bExtremeInput = false; + if (LastPreAdditiveVRVelocity.SizeSquared() > FMath::Square(TrackingLossThreshold)) + { + // Default to always holding position during flight to avoid too much velocity injection + AdditionalVRInputVector = FVector::ZeroVector; + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = Velocity * deltaTime; + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted + AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + bSteppedUp = StepUp(GravDir, (Adjusted + AdditionalVRInputVector) * (1.f - Hit.Time) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, nullptr); + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + + if (!bJustTeleported) + { + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + //Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) - AdditionalVRInputVector) / deltaTime; + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)) / deltaTime; + } + + RestorePreAdditiveVRMotionVelocity(); + } +} + +void UVRCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysFalling); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime); + const FVector GravityRelativeFallAcceleration = RotateWorldToGravity(FallAcceleration); + FallAcceleration = RotateGravityToWorld(FVector(GravityRelativeFallAcceleration.X, GravityRelativeFallAcceleration.Y, 0)); + const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration); + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + float remainingTime = deltaTime; + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) + { + Iterations++; + float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation; + + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + bJustTeleported = false; + + const FVector OldVelocityWithRootMotion = Velocity; + + RestorePreAdditiveRootMotionVelocity(); + // RestorePreAdditiveVRMotionVelocity(); + + const FVector OldVelocity = Velocity; + + // Apply input + const float MaxDecel = GetMaxBrakingDeceleration(); + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // Compute Velocity + { + // Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it. + TGuardValue RestoreAcceleration(Acceleration, FallAcceleration); + if (HasCustomGravity()) + { + Velocity = FVector::VectorPlaneProject(Velocity, RotateGravityToWorld(FVector::UpVector)); + const FVector GravityRelativeOffset = OldVelocity - Velocity; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + Velocity += GravityRelativeOffset; + } + else + { + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + Velocity.Z = OldVelocity.Z; + } + } + } + + //Velocity += CustomVRInputVector / deltaTime; + + // Compute current gravity + const FVector Gravity = -GetGravityDirection() * GetGravityZ(); + + float GravityTime = timeTick; + + // If jump is providing force, gravity may be affected. + bool bEndingJumpForce = false; + if (CharacterOwner->JumpForceTimeRemaining > 0.0f) + { + // Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false. + const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick); + GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime); + + // Update Character state + CharacterOwner->JumpForceTimeRemaining -= JumpForceTime; + if (CharacterOwner->JumpForceTimeRemaining <= 0.0f) + { + CharacterOwner->ResetJumpState(); + bEndingJumpForce = true; + } + } + + // Apply gravity + Velocity = NewFallVelocity(Velocity, Gravity, GravityTime); + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) OldVelocityWithRootMotion=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *OldVelocityWithRootMotion.ToString(), *Velocity.ToString()); + ApplyRootMotionToVelocity(timeTick); + DecayFormerBaseVelocity(timeTick); + + // See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies. + const FVector GravityRelativeOldVelocityWithRootMotion = RotateWorldToGravity(OldVelocityWithRootMotion); + static const auto CVarForceJumpPeakSubstep = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ForceJumpPeakSubstep")); + if (CVarForceJumpPeakSubstep->GetInt() != 0 && GravityRelativeOldVelocityWithRootMotion.Z > 0.f && RotateWorldToGravity(Velocity).Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation) + { + const FVector DerivedAccel = (Velocity - OldVelocityWithRootMotion) / timeTick; + const FVector GravityRelativeDerivedAccel = RotateWorldToGravity(DerivedAccel); + if (!FMath::IsNearlyZero(GravityRelativeDerivedAccel.Z)) + { + const float TimeToApex = -GravityRelativeOldVelocityWithRootMotion.Z / GravityRelativeDerivedAccel.Z; + + // The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work. + const float ApexTimeMinimum = 0.0001f; + if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick) + { + const FVector ApexVelocity = OldVelocityWithRootMotion + (DerivedAccel * TimeToApex); + if (HasCustomGravity()) + { + const FVector GravityRelativeApexVelocity = RotateWorldToGravity(ApexVelocity); + Velocity = RotateGravityToWorld(FVector(GravityRelativeApexVelocity.X, GravityRelativeApexVelocity.Y, 0)); // Should be nearly zero anyway, but this makes apex notifications consistent. + } + else + { + Velocity = ApexVelocity; + Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent. + + } + + // We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration. + const float TimeToRefund = (timeTick - TimeToApex); + + remainingTime += TimeToRefund; + timeTick = TimeToApex; + Iterations--; + NumJumpApexAttempts++; + + // Refund time to any active Root Motion Sources as well + for (TSharedPtr RootMotionSource : CurrentRootMotion.RootMotionSources) + { + const float RewoundRMSTime = FMath::Max(0.0f, RootMotionSource->GetTime() - TimeToRefund); + RootMotionSource->SetTime(RewoundRMSTime); + } + } + } + } + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *Velocity.ToString()); + + ApplyRootMotionToVelocity(timeTick); + //ApplyVRMotionToVelocity(deltaTime); + + if (bNotifyApex && (RotateWorldToGravity(Velocity).Z < 0.f)) + { + // Just passed jump apex since now going down + bNotifyApex = false; + NotifyJumpApex(); + } + + // Compute change in position (using midpoint integration method). + FVector Adjusted = (0.5f * (OldVelocityWithRootMotion + Velocity) * timeTick) + ((AdditionalVRInputVector / deltaTime) * timeTick); + + ApplyVRMotionToVelocity(deltaTime); + + // Special handling if ending the jump force where we didn't apply gravity during the jump. + if (bEndingJumpForce && !bApplyGravityWhileJumping) + { + // We had a portion of the time at constant speed then a portion with acceleration due to gravity. + // Account for that here with a more correct change in position. + const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime); + Adjusted = ((OldVelocityWithRootMotion * NonGravityTime) + (0.5f * (OldVelocityWithRootMotion + Velocity) * GravityTime)) /*+ ((AdditionalVRInputVector / deltaTime) * timeTick)*/; + } + + // Move + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit); + + if (!HasValidData()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + float LastMoveTimeSlice = timeTick; + float subTimeTickRemaining = timeTick * (1.f - Hit.Time); + + if (IsSwimming()) //just entered water + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + else if (Hit.bBlockingHit) + { + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit)) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else + { + // Compute impact deflection based on final velocity, not integration step. + // This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result. + Adjusted = Velocity * timeTick; + + // See if we can convert a normally invalid landing spot (based on the hit result) to a usable one. + if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit)) + { + /*const */FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + if (VRRootCapsule) + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + FFindFloorResult FloorResult; + FindFloor(PawnLocation, FloorResult, false, NULL); + if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult)) + { + //RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(FloorResult.HitResult, remainingTime, Iterations); + + if (bAutoOrientToFloorNormal && FloorResult.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(FloorResult.HitResult, timeTick); + } + return; + } + } + + HandleImpact(Hit, LastMoveTimeSlice, Adjusted); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + // Limit air control based on what we hit. + // We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration. + FVector VelocityNoAirControl = OldVelocity; + FVector AirControlAccel = Acceleration; + if (bHasLimitedAirControl) + { + // Compute VelocityNoAirControl + { + // Find velocity *without* acceleration. + TGuardValue RestoreAcceleration(Acceleration, FVector::ZeroVector); + TGuardValue RestoreVelocity(Velocity, OldVelocity); + if (HasCustomGravity()) + { + Velocity = FVector::VectorPlaneProject(Velocity, RotateGravityToWorld(FVector::UpVector)); + const FVector GravityRelativeOffset = OldVelocity - Velocity; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + VelocityNoAirControl = Velocity + GravityRelativeOffset; + } + else + { + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z); + } + VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime); + } + + const bool bCheckLandingSpot = false; // we already checked above. + AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick; + const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice; + Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice; + } + + const FVector OldHitNormal = Hit.Normal; + const FVector OldHitImpactNormal = Hit.ImpactNormal; + FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit); + + // Compute velocity after deflection (only gravity component for RootMotion) + const UPrimitiveComponent* HitComponent = Hit.GetComponent(); + static const auto CVarUseTargetVelocityOnImpact = IConsoleManager::Get().FindConsoleVariable(TEXT("p.UseTargetVelocityOnImpact")); + if (CVarUseTargetVelocityOnImpact->GetInt() && !Velocity.IsNearlyZero() && MovementBaseUtility::IsSimulatedBase(HitComponent)) + { + const FVector ContactVelocity = MovementBaseUtility::GetMovementBaseVelocity(HitComponent, NAME_None) + MovementBaseUtility::GetMovementBaseTangentialVelocity(HitComponent, NAME_None, Hit.ImpactPoint); + const FVector NewVelocity = Velocity - Hit.ImpactNormal * FVector::DotProduct(Velocity - ContactVelocity, Hit.ImpactNormal); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + else if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f) + { + // Move in deflected direction. + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + + if (Hit.bBlockingHit) + { + // hit second wall + LastMoveTimeSlice = subTimeTickRemaining; + subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time); + + + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit)) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + + HandleImpact(Hit, LastMoveTimeSlice, Delta); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + // Act as if there was no air control on the last move when computing new deflection. + if (bHasLimitedAirControl && RotateWorldToGravity(Hit.Normal).Z > CharacterMovementConstants::VERTICAL_SLOPE_NORMAL_ZVR) + { + const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice; + Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit); + } + + FVector PreTwoWallDelta = Delta; + TwoWallAdjust(Delta, Hit, OldHitNormal); + + // Limit air control, but allow a slide along the second wall. + if (bHasLimitedAirControl) + { + const bool bCheckLandingSpot = false; // we already checked above. + const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining; + + // Only allow if not back in to first wall + if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f) + { + Delta += (AirControlDeltaV * subTimeTickRemaining); + } + } + + // Compute velocity after deflection (only gravity component for RootMotion) + if (subTimeTickRemaining > UE_KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + // bDitch=true means that pawn is straddling two slopes, neither of which he can stand on + bool bDitch = ((RotateWorldToGravity(OldHitImpactNormal).Z > 0.f) && (RotateWorldToGravity(Hit.ImpactNormal).Z > 0.f) && (FMath::Abs(Delta.Z) <= UE_KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f)); + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + if (Hit.Time == 0.f) + { + // if we are stuck then try to side step + FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D(); + if (SideDelta.IsNearlyZero()) + { + SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal(); + } + SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit); + } + + if (bDitch || IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit) || Hit.Time == 0.f) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime = 0.f; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && RotateWorldToGravity(OldHitImpactNormal).Z >= GetWalkableFloorZ()) + { + // We might be in a virtual 'ditch' within our perch radius. This is rare. + const FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + const float ZMovedDist = FMath::Abs(RotateWorldToGravity(PawnLocation - OldLocation).Z); + const float MovedDist2DSq = FVector::VectorPlaneProject(PawnLocation - OldLocation, RotateGravityToWorld(FVector::UpVector)).Size2D(); + if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick) + { + FVector GravityRelativeVelocity = RotateWorldToGravity(Velocity); + GravityRelativeVelocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + GravityRelativeVelocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + GravityRelativeVelocity.Z = FMath::Max(JumpZVelocity * 0.25f, 1.f); + Velocity = RotateGravityToWorld(GravityRelativeVelocity); + Delta = Velocity * timeTick; + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + } + } + } + } + } + } + else + { + // We are finding the floor and adjusting here now + // This matches the final falling Z up to the PhysWalking floor offset to prevent the visible hitch when going + // From falling to walking. + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false, NULL); + + if (CurrentFloor.IsWalkableFloor()) + { + // If the current floor distance is within the physwalking required floor offset + if (CurrentFloor.GetDistanceToFloor() < (MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2) + { + // Adjust to correct height + AdjustFloorHeight(); + + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + + // If this is a valid landing spot, stop falling now so that we land correctly + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, CurrentFloor.HitResult)) + { + remainingTime += subTimeTickRemaining; + ProcessLanded(CurrentFloor.HitResult, remainingTime, Iterations); + return; + } + } + } + else if (CurrentFloor.HitResult.bStartPenetrating) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit2(CurrentFloor.HitResult); + Hit.TraceEnd = Hit2.TraceStart + RotateGravityToWorld(FVector(0.f, 0.f, MAX_FLOOR_DIST)); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit2); + ResolvePenetration(RequestedAdjustment, Hit2, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, timeTick); + } + } + + FVector GravityRelativeVelocity = RotateWorldToGravity(Velocity); + if (GravityRelativeVelocity.SizeSquared2D() <= UE_KINDA_SMALL_NUMBER * 10.f) + { + GravityRelativeVelocity.X = 0.f; + GravityRelativeVelocity.Y = 0.f; + Velocity = RotateGravityToWorld(GravityRelativeVelocity); + } + + RestorePreAdditiveVRMotionVelocity(); + } +} + + +void UVRCharacterMovementComponent::PhysNavWalking(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysNavWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Root motion not for VR + if ((!CharacterOwner || !CharacterOwner->Controller) && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN before CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + //bound acceleration + Acceleration.Z = 0.f; + //if (!HasRootMotion()) + //{ + CalcVelocity(deltaTime, GroundFriction, false, BrakingDecelerationWalking); + devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + //} + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + /*if (IsFalling()) + { + // Root motion could have put us into Falling + StartNewPhysics(deltaTime, Iterations); + return; + }*/ + + Iterations++; + + FVector DesiredMove = Velocity; + DesiredMove.Z = 0.f; + + //const FVector OldPlayerLocation = GetActorFeetLocation(); + const FVector OldLocation = GetActorFeetLocationVR(); + const FVector DeltaMove = DesiredMove * deltaTime; + const bool bDeltaMoveNearlyZero = DeltaMove.IsNearlyZero(); + + FVector AdjustedDest = OldLocation + DeltaMove; + FNavLocation DestNavLocation; + + bool bSameNavLocation = false; + if (CachedNavLocation.NodeRef != INVALID_NAVNODEREF) + { + if (bProjectNavMeshWalking) + { + const float DistSq2D = (OldLocation - CachedNavLocation.Location).SizeSquared2D(); + const float DistZ = FMath::Abs(OldLocation.Z - CachedNavLocation.Location.Z); + + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float ProjectionScale = (OldLocation.Z > CachedNavLocation.Location.Z) ? NavMeshProjectionHeightScaleUp : NavMeshProjectionHeightScaleDown; + const float DistZThr = TotalCapsuleHeight * FMath::Max(0.f, ProjectionScale); + + bSameNavLocation = (DistSq2D <= UE_KINDA_SMALL_NUMBER) && (DistZ < DistZThr); + } + else + { + bSameNavLocation = CachedNavLocation.Location.Equals(OldLocation); + } + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + if (const INavigationDataInterface * NavData = GetNavData()) + { + if (!NavData->IsNodeRefValid(CachedNavLocation.NodeRef)) + { + CachedNavLocation.NodeRef = INVALID_NAVNODEREF; + bSameNavLocation = false; + } + } + } + } + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + DestNavLocation = CachedNavLocation; + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("%s using cached navmesh location! (bProjectNavMeshWalking = %d)"), *GetNameSafe(CharacterOwner), bProjectNavMeshWalking); + } + else + { + SCOPE_CYCLE_COUNTER(STAT_CharNavProjectPoint); + + // Start the trace from the Z location of the last valid trace. + // Otherwise if we are projecting our location to the underlying geometry and it's far above or below the navmesh, + // we'll follow that geometry's plane out of range of valid navigation. + if (bSameNavLocation && bProjectNavMeshWalking) + { + AdjustedDest.Z = CachedNavLocation.Location.Z; + } + + // Find the point on the NavMesh + const bool bHasNavigationData = FindNavFloor(AdjustedDest, DestNavLocation); + if (!bHasNavigationData) + { + RestorePreAdditiveVRMotionVelocity(); + SetMovementMode(MOVE_Walking); + return; + } + + CachedNavLocation = DestNavLocation; + } + + if (DestNavLocation.NodeRef != INVALID_NAVNODEREF) + { + FVector NewLocation(AdjustedDest.X, AdjustedDest.Y, DestNavLocation.Location.Z); + if (bProjectNavMeshWalking) + { + SCOPE_CYCLE_COUNTER(STAT_CharNavProjectLocation); + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float UpOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleUp); + const float DownOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleDown); + NewLocation = ProjectLocationFromNavMesh(deltaTime, OldLocation, NewLocation, UpOffset, DownOffset); + } + + FVector AdjustedDelta = NewLocation - OldLocation; + + if (!AdjustedDelta.IsNearlyZero()) + { + // 4.16 UNCOMMENT + FHitResult HitResult; + SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweepWhileNavWalking, HitResult); + + /* 4.16 Delete*/ + //const bool bSweep = UpdatedPrimitive ? UpdatedPrimitive->bGenerateOverlapEvents : false; + //FHitResult HitResult; + //SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweep, HitResult); + // End 4.16 delete + } + + // Update velocity to reflect actual move + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasVelocity()) + { + Velocity = (GetActorFeetLocationVR() - OldLocation) / deltaTime; + MaintainHorizontalGroundVelocity(); + } + + bJustTeleported = false; + } + else + { + StartFalling(Iterations, deltaTime, deltaTime, DeltaMove, OldLocation); + } + + RestorePreAdditiveVRMotionVelocity(); +} + +void UVRCharacterMovementComponent::PhysSwimming(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + float NetFluidFriction = 0.f; + float Depth = ImmersionDepth(); + float NetBuoyancy = Buoyancy * Depth; + float OriginalAccelZ = Acceleration.Z; + bool bLimitedUpAccel = false; + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 0.33f * MaxSwimSpeed) && (NetBuoyancy != 0.f)) + { + //damp positive Z out of water + Velocity.Z = FMath::Max(0.33f * MaxSwimSpeed, Velocity.Z * Depth * Depth); + } + else if (Depth < 0.65f) + { + bLimitedUpAccel = (Acceleration.Z > 0.f); + Acceleration.Z = FMath::Min(0.1f, Acceleration.Z); + } + + Iterations++; + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + bJustTeleported = false; + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction * Depth; + CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration()); + Velocity.Z += GetGravityZ() * deltaTime * (1.f - NetBuoyancy); + } + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + FVector Adjusted = Velocity * deltaTime; + FHitResult Hit(1.f); + float remainingTime = deltaTime * SwimVR(Adjusted/* + AdditionalVRInputVector*/, Hit); + + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + + if (Hit.Time < 1.f && CharacterOwner) + { + HandleSwimmingWallHit(Hit, deltaTime); + if (bLimitedUpAccel && (Velocity.Z >= 0.f)) + { + // allow upward velocity at surface if against obstacle + Velocity.Z += OriginalAccelZ * deltaTime; + Adjusted = Velocity * (1.f - Hit.Time)*deltaTime; + SwimVR(Adjusted, Hit); + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + } + + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + const FVector RealVelocity = Velocity; + Velocity.Z = 1.f; // HACK: since will be moving up, in case pawn leaves the water + bSteppedUp = StepUp(GravDir, (Adjusted/* + AdditionalVRInputVector*/) * (1.f - Hit.Time), Hit); + if (bSteppedUp) + { + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + Velocity = RealVelocity; + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported && ((deltaTime - remainingTime) > UE_KINDA_SMALL_NUMBER) && CharacterOwner) + { + bool bWaterJump = !GetPhysicsVolume()->bWaterVolume; + float velZ = Velocity.Z; + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)/* - AdditionalVRInputVector*/) / (deltaTime - remainingTime); + if (bWaterJump) + { + Velocity.Z = velZ; + } + } + + if (!GetPhysicsVolume()->bWaterVolume && IsSwimming()) + { + SetMovementMode(MOVE_Falling); //in case script didn't change it (w/ zone change) + } + + RestorePreAdditiveVRMotionVelocity(); + + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + StartNewPhysics(remainingTime, Iterations); + } +} + + +void UVRCharacterMovementComponent::StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations) +{ + if (remainingTime < MIN_TICK_TIME || timeTick < MIN_TICK_TIME) + { + return; + } + + FVector NewLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported) + { + Velocity = (NewLocation - OldLocation) / timeTick; //actual average velocity + Velocity = 2.f*Velocity - OldVelocity; //end velocity has 2* accel of avg + Velocity = Velocity.GetClampedToMaxSize(GetPhysicsVolume()->TerminalVelocity); + } + const FVector End = FindWaterLine(NewLocation, OldLocation); + float waterTime = 0.f; + if (End != NewLocation) + { + const float ActualDist = (NewLocation - OldLocation).Size(); + if (ActualDist > UE_KINDA_SMALL_NUMBER) + { + waterTime = timeTick * (End - NewLocation).Size() / ActualDist; + remainingTime += waterTime; + } + MoveUpdatedComponent(End - NewLocation, UpdatedComponent->GetComponentQuat(), true); + } + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 2.f* CharacterMovementConstants::SWIMBOBSPEEDVR) && (Velocity.Z < 0.f)) //allow for falling out of water + { + Velocity.Z = CharacterMovementConstants::SWIMBOBSPEEDVR - Velocity.Size2D() * 0.7f; //smooth bobbing + } + if ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) + { + PhysSwimming(remainingTime, Iterations); + } +} + +float UVRCharacterMovementComponent::SwimVR(FVector Delta, FHitResult& Hit) +{ + FVector Start = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + float airTime = 0.f; + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (!GetPhysicsVolume()->bWaterVolume) //then left water + { + FVector NewLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + const FVector End = FindWaterLine(Start, NewLoc); + const float DesiredDist = Delta.Size(); + if (End != NewLoc && DesiredDist > UE_KINDA_SMALL_NUMBER) + { + airTime = (End - NewLoc).Size() / DesiredDist; + if (((NewLoc - Start) | (End - NewLoc)) > 0.f) + { + airTime = 0.f; + } + SafeMoveUpdatedComponent(End - NewLoc, UpdatedComponent->GetComponentQuat(), true, Hit); + } + } + return airTime; +} + +bool UVRCharacterMovementComponent::CheckWaterJump(FVector CheckPoint, FVector& WallNormal) +{ + if (!HasValidData()) + { + return false; + } + FVector currentLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + // check if there is a wall directly in front of the swimming pawn + CheckPoint.Z = 0.f; + FVector CheckNorm = CheckPoint.GetSafeNormal(); + float PawnCapsuleRadius, PawnCapsuleHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnCapsuleRadius, PawnCapsuleHalfHeight); + CheckPoint = currentLoc + 1.2f * PawnCapsuleRadius * CheckNorm; + FVector Extent(PawnCapsuleRadius, PawnCapsuleRadius, PawnCapsuleHalfHeight); + FHitResult HitInfo(1.f); + FCollisionQueryParams CapsuleParams(SCENE_QUERY_STAT(CheckWaterJump), false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + FCollisionShape CapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_None); + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + bool bHit = GetWorld()->SweepSingleByChannel(HitInfo, currentLoc, CheckPoint, FQuat::Identity, CollisionChannel, CapsuleShape, CapsuleParams, ResponseParam); + + if (bHit && !HitInfo.HitObjectHandle.DoesRepresentClass(APawn::StaticClass())) + { + // hit a wall - check if it is low enough + WallNormal = -1.f * HitInfo.ImpactNormal; + FVector Start = currentLoc;//UpdatedComponent->GetComponentLocation(); + Start.Z += MaxOutOfWaterStepHeight; + CheckPoint = Start + 3.2f * PawnCapsuleRadius * WallNormal; + FCollisionQueryParams LineParams(SCENE_QUERY_STAT(CheckWaterJump), true, CharacterOwner); + FCollisionResponseParams LineResponseParam; + InitCollisionParams(LineParams, LineResponseParam); + bHit = GetWorld()->LineTraceSingleByChannel(HitInfo, Start, CheckPoint, CollisionChannel, LineParams, LineResponseParam); + // if no high obstruction, or it's a valid floor, then pawn can jump out of water + return !bHit || IsWalkable(HitInfo); + } + return false; +} + +FBasedPosition UVRCharacterMovementComponent::GetActorFeetLocationBased() const +{ + return FBasedPosition(NULL, GetActorFeetLocationVR()); +} + +void UVRCharacterMovementComponent::ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharProcessLanded); + + if (CharacterOwner && CharacterOwner->ShouldNotifyLanded(Hit)) + { + CharacterOwner->Landed(Hit); + } + if (IsFalling()) + { + + if (GetGroundMovementMode() == MOVE_NavWalking) + { + // verify navmesh projection and current floor + // otherwise movement will be stuck in infinite loop: + // navwalking -> (no navmesh) -> falling -> (standing on something) -> navwalking -> .... + + const FVector TestLocation = GetActorFeetLocationVR(); + FNavLocation NavLocation; + + const bool bHasNavigationData = FindNavFloor(TestLocation, NavLocation); + if (!bHasNavigationData || NavLocation.NodeRef == INVALID_NAVNODEREF) + { + SetGroundMovementMode(MOVE_Walking); + //GroundMovementMode = MOVE_Walking; + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("ProcessLanded(): %s tried to go to NavWalking but couldn't find NavMesh! Using Walking instead."), *GetNameSafe(CharacterOwner)); + } + } + + SetPostLandedPhysics(Hit); + } + + IPathFollowingAgentInterface* PFAgent = GetPathFollowingAgent(); + if (PFAgent) + { + PFAgent->OnLanded(); + } + + StartNewPhysics(remainingTime, Iterations); +} + +/////////////////////////// +// End Navigation Functions +/////////////////////////// + +void UVRCharacterMovementComponent::PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction& ThisTickFunction) +{ + if (bDeferUpdateBasedMovement) + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + UpdateBasedMovement(DeltaTime); + SaveBaseLocation(); + bDeferUpdateBasedMovement = false; + } +} + + +void UVRCharacterMovementComponent::SimulateMovement(float DeltaSeconds) +{ + if (!HasValidData() || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics()) + { + return; + } + + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const FRepMovement& ConstRepMovement = CharacterOwner->GetReplicatedMovement(); + + // Workaround for replication not being updated initially + if (bIsSimulatedProxy && + ConstRepMovement.Location.IsZero() && + ConstRepMovement.Rotation.IsZero() && + ConstRepMovement.LinearVelocity.IsZero()) + { + return; + } + + // If base is not resolved on the client, we should not try to simulate at all + if (CharacterOwner->GetReplicatedBasedMovement().IsBaseUnresolved()) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Base for simulated character '%s' is not resolved on client, skipping SimulateMovement"), *CharacterOwner->GetName()); + return; + } + + FVector OldVelocity; + FVector OldLocation; + + // Scoped updates can improve performance of multiple MoveComponent calls. + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + bool bHandledNetUpdate = false; + if (bIsSimulatedProxy) + { + // Handle network changes + if (bNetworkUpdateReceived) + { + bNetworkUpdateReceived = false; + bHandledNetUpdate = true; + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s received net update"), *GetNameSafe(CharacterOwner)); + if (bNetworkGravityDirectionChanged) + { + SetGravityDirection(CharacterOwner->GetReplicatedGravityDirection()); + bNetworkGravityDirectionChanged = false; + } + + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + else if (bJustTeleported || bForceNextFloorCheck) + { + // Make sure floor is current. We will continue using the replicated base, if there was one. + bJustTeleported = false; + UpdateFloorFromAdjustment(); + } + } + else if (bForceNextFloorCheck) + { + UpdateFloorFromAdjustment(); + } + } + + UpdateCharacterStateBeforeMovement(DeltaSeconds); + + if (MovementMode != MOVE_None) + { + //TODO: Also ApplyAccumulatedForces()? + HandlePendingLaunch(); + } + ClearAccumulatedForces(); + + if (MovementMode == MOVE_None) + { + return; + } + + const bool bSimGravityDisabled = (bIsSimulatedProxy && CharacterOwner->bSimGravityDisabled); + const bool bZeroReplicatedGroundVelocity = (bIsSimulatedProxy && IsMovingOnGround() && ConstRepMovement.LinearVelocity.IsZero()); + + // bSimGravityDisabled means velocity was zero when replicated and we were stuck in something. Avoid external changes in velocity as well. + // Being in ground movement with zero velocity, we cannot simulate proxy velocities safely because we might not get any further updates from the server. + if (bSimGravityDisabled || bZeroReplicatedGroundVelocity) + { + Velocity = FVector::ZeroVector; + } + + + MaybeUpdateBasedMovement(DeltaSeconds); + + // simulated pawns predict location + OldVelocity = Velocity; + OldLocation = UpdatedComponent->GetComponentLocation(); + + UpdateProxyAcceleration(); + + static const auto CVarNetEnableSkipProxyPredictionOnNetUpdate = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableSkipProxyPredictionOnNetUpdate")); + // May only need to simulate forward on frames where we haven't just received a new position update. + if (!bHandledNetUpdate || !bNetworkSkipProxyPredictionOnNetUpdate || !CVarNetEnableSkipProxyPredictionOnNetUpdate->GetInt()) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s simulating movement"), *GetNameSafe(CharacterOwner)); + FStepDownResult StepDownResult; + + // Skip the estimated movement when movement simulation is off, but keep the floor find + if(!bDisableSimulatedTickWhenSmoothingMovement) + { + MoveSmooth(Velocity, DeltaSeconds, &StepDownResult); + } + + // find floor and check if falling + if (IsMovingOnGround() || MovementMode == MOVE_Falling) + { + bool bShouldFindFloor = Velocity.Z <= 0.f; + if (HasCustomGravity()) + { + bShouldFindFloor = RotateWorldToGravity(Velocity).Z <= 0.0; + } + + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else if (bDisableSimulatedTickWhenSmoothingMovement || bShouldFindFloor) + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, Velocity.IsZero(), NULL); + } + else + { + CurrentFloor.Clear(); + } + + // Possible for dynamic movement bases, particularly those that align to slopes while the character does not, to encroach the character. + // Check to see if we can resolve the penetration in those cases, and if so find the floor. + if (CurrentFloor.HitResult.bStartPenetrating && MovementBaseUtility::IsDynamicBase(GetMovementBase())) + { + // Follows PhysWalking approach for encroachment on floor tests + FHitResult Hit(CurrentFloor.HitResult); + if (HasCustomGravity()) + { + Hit.TraceEnd = Hit.TraceStart - GetGravityDirection() * MAX_FLOOR_DIST; + } + else + { + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + } + + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + const bool bResolved = ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck |= bResolved; + + if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, DeltaSeconds); + } + } + else if (!CurrentFloor.IsWalkableFloor()) + { + if (!bSimGravityDisabled) + { + // No floor, must fall. + if (HasCustomGravity()) + { + if (RotateWorldToGravity(Velocity).Z <= 0.f || bApplyGravityWhileJumping || !CharacterOwner->IsJumpProvidingForce()) + { + Velocity = NewFallVelocity(Velocity, -GetGravityDirection() * GetGravityZ(), DeltaSeconds); + } + } + else if (Velocity.Z <= 0.f || bApplyGravityWhileJumping || !CharacterOwner->IsJumpProvidingForce()) + { + Velocity = NewFallVelocity(Velocity, FVector(0.f, 0.f, GetGravityZ()), DeltaSeconds); + } + } + + SetMovementMode(MOVE_Falling); + } + else + { + // Walkable floor + if (IsMovingOnGround()) + { + AdjustFloorHeight(); + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (MovementMode == MOVE_Falling) + { + if (CurrentFloor.FloorDist <= MIN_FLOOR_DIST || (bSimGravityDisabled && CurrentFloor.FloorDist <= MAX_FLOOR_DIST)) + { + // Landed + SetPostLandedPhysics(CurrentFloor.HitResult); + } + else + { + if (!bSimGravityDisabled) + { + // Continue falling. + Velocity = NewFallVelocity(Velocity, -GetGravityDirection() * GetGravityZ(), DeltaSeconds); + } + CurrentFloor.Clear(); + } + } + + if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, DeltaSeconds); + } + } + } + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s SKIPPING simulate movement"), *GetNameSafe(CharacterOwner)); + } + + UpdateCharacterStateAfterMovement(DeltaSeconds); + + // consume path following requested velocity + LastUpdateRequestedVelocity = bHasRequestedVelocity ? RequestedVelocity : FVector::ZeroVector; + bHasRequestedVelocity = false; + + OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); + } // End scoped movement update + + // Call custom post-movement events. These happen after the scoped movement completes in case the events want to use the current state of overlaps etc. + CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity); + + static const auto CVarBasedMovementMode = IConsoleManager::Get().FindConsoleVariable(TEXT("p.BasedMovementMode")); + if (CVarBasedMovementMode->GetInt() == 0) + { + SaveBaseLocation(); // behaviour before implementing this fix + } + else + { + MaybeSaveBaseLocation(); + } + + UpdateComponentVelocity(); + bJustTeleported = false; + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; +} + +void UVRCharacterMovementComponent::MoveSmooth(const FVector& InVelocity, const float DeltaSeconds, FStepDownResult* OutStepDownResult) +{ + if (!HasValidData()) + { + return; + } + + // Custom movement mode. + // Custom movement may need an update even if there is zero velocity. + if (MovementMode == MOVE_Custom) + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + PhysCustom(DeltaSeconds, 0); + return; + } + + FVector Delta = InVelocity * DeltaSeconds; + if (Delta.IsZero()) + { + return; + } + + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + if (IsMovingOnGround()) + { + MoveAlongFloor(InVelocity, DeltaSeconds, OutStepDownResult); + } + else + { + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.IsValidBlockingHit()) + { + bool bSteppedUp = false; + + if (IsFlying()) + { + if (CanStepUp(Hit)) + { + OutStepDownResult = NULL; // No need for a floor when not walking. + bool bShouldAttemptStepUp = false; + bShouldAttemptStepUp = FMath::Abs(RotateWorldToGravity(Hit.ImpactNormal).Z) < 0.2; + if (bShouldAttemptStepUp) + { + const FVector GravDir = GetGravityDirection(); + const FVector DesiredDir = Delta.GetSafeNormal(); + const float UpDown = GravDir | DesiredDir; + if ((UpDown < 0.5f) && (UpDown > -0.2f)) + { + bSteppedUp = StepUp(GravDir, Delta * (1.f - Hit.Time), Hit, OutStepDownResult); + } + } + } + } + + // If StepUp failed, try sliding. + if (!bSteppedUp) + { + SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, false); + } + } + } +} + +void UVRCharacterMovementComponent::ClientHandleMoveResponse(const FCharacterMoveResponseDataContainer& MoveResponse) +{ + if (MoveResponse.IsGoodMove()) + { + ClientAckGoodMove_Implementation(MoveResponse.ClientAdjustment.TimeStamp); + } + else + { + // Wrappers to old RPC handlers, to maintain compatibility. If overrides need additional serialized data, they can access GetMoveResponseDataContainer() + if (MoveResponse.bRootMotionSourceCorrection) + { + if (FRootMotionSourceGroup* RootMotionSourceGroup = MoveResponse.GetRootMotionSourceGroup(*this)) + { + ClientAdjustRootMotionSourcePosition_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + *RootMotionSourceGroup, + MoveResponse.bRootMotionMontageCorrection, + MoveResponse.RootMotionTrackPosition, + MoveResponse.ClientAdjustment.NewLoc, + MoveResponse.RootMotionRotation, + MoveResponse.ClientAdjustment.NewVel.Z, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode); + } + } + else if (MoveResponse.bRootMotionMontageCorrection) + { + ClientAdjustRootMotionPosition_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + MoveResponse.RootMotionTrackPosition, + MoveResponse.ClientAdjustment.NewLoc, + MoveResponse.RootMotionRotation, + MoveResponse.ClientAdjustment.NewVel.Z, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode); + } + else + { + ClientAdjustPositionVR_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + MoveResponse.ClientAdjustment.NewLoc, + //FRotator::CompressAxisToShort(MoveResponse.ClientAdjustment.NewRot.Yaw), + MoveResponse.ClientAdjustment.NewVel, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode, + MoveResponse.bHasRotation ? MoveResponse.ClientAdjustment.NewRot : TOptional(), + MoveResponse.ClientAdjustment.GravityDirection + ); + + // #TODO: Epic added rotation adjustment in 5.1 + //MoveResponse.bHasRotation ? MoveResponse.ClientAdjustment.NewRot : TOptional() + } + } +} + +// #TODO: Epic added their own rotation correct in 5.1 +//TOptional OptionalRotation /* = TOptional()*/ +void UVRCharacterMovementComponent::ClientAdjustPositionVR_Implementation +( + float TimeStamp, + FVector NewLocation, + //uint16 NewYaw, + FVector NewVelocity, + UPrimitiveComponent* NewBase, + FName NewBaseBoneName, + bool bHasBase, + bool bBaseRelativePosition, + uint8 ServerMovementMode, + TOptional OptionalRotation, + TOptional OptionalGravityDirection +) +{ + if (!HasValidData() || !IsActive()) + { + return; + } + + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + check(ClientData); + + // Make sure the base actor exists on this client. + const bool bUnresolvedBase = bHasBase && (NewBase == NULL); + if (bUnresolvedBase) + { + if (bBaseRelativePosition) + { + UE_LOG(LogNetPlayerMovement, Warning, TEXT("ClientAdjustPosition_Implementation could not resolve the new relative movement base actor, ignoring server correction! Client currently at world location %s on base %s"), + *UpdatedComponent->GetComponentLocation().ToString(), *GetNameSafe(GetMovementBase())); + return; + } + else + { + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientAdjustPosition_Implementation could not resolve the new absolute movement base actor, but WILL use the position!")); + } + } + + // Ack move if it has not expired. + int32 MoveIndex = ClientData->GetSavedMoveIndex(TimeStamp); + if (MoveIndex == INDEX_NONE) + { + if (ClientData->LastAckedMove.IsValid()) + { + UE_LOG(LogNetPlayerMovement, Log, TEXT("ClientAdjustPosition_Implementation could not find Move for TimeStamp: %f, LastAckedTimeStamp: %f, CurrentTimeStamp: %f"), TimeStamp, ClientData->LastAckedMove->TimeStamp, ClientData->CurrentTimeStamp); + } + return; + } + + // Trying to use epics rotation code now + // Comment this when we port to it + /*if (!bUseClientControlRotation) + { + float YawValue = FRotator::DecompressAxisFromShort(NewYaw); + // Trust the server's control yaw + if (ClientData->LastAckedMove.IsValid() && !FMath::IsNearlyEqual(ClientData->LastAckedMove->SavedControlRotation.Yaw, YawValue)) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->bUseControllerRotationYaw) + { + AController * myController = BaseVRCharacterOwner->GetController(); + if (myController) + { + //FRotator newRot = myController->GetControlRotation(); + myController->SetControlRotation(FRotator(0.f, YawValue, 0.f));//(ClientData->LastAckedMove->SavedControlRotation); + } + } + + BaseVRCharacterOwner->SetActorRotation(FRotator(0.f, YawValue, 0.f)); + } + } + }*/ + + ClientData->AckMove(MoveIndex, *this); + + FVector WorldShiftedNewLocation; + // Received Location is relative to dynamic base + if (bBaseRelativePosition) + { + MovementBaseUtility::TransformLocationToWorld(NewBase, NewBaseBoneName, NewLocation, WorldShiftedNewLocation); // TODO: error handling if returns false + } + else + { + WorldShiftedNewLocation = FRepMovement::RebaseOntoLocalOrigin(NewLocation, this); + } + + // Server's world velocity may need to be converted to velocity relative to the movement base orientation, if the base orientations don't match. + const FCharacterMoveResponseDataContainer& ResponseDataContainer = GetMoveResponseDataContainer(); + if (ResponseDataContainer.ClientAdjustment.bBaseRelativeVelocity) + { + // Convert Relative Velocity -> World Velocity + const FVector CurrentVelocity = NewVelocity; + MovementBaseUtility::TransformDirectionToWorld(NewBase, NewBaseBoneName, CurrentVelocity, NewVelocity); + } + + + // #TODO: Epics 5.1 rotation enforce, we use our own currently + + // Fall back to the last-known good rotation if the server didn't send one + /*static const auto CVarUseLastGoodRotationDuringCorrection = IConsoleManager::Get().FindConsoleVariable(TEXT("p.UseLastGoodRotationDuringCorrection")); + if (CVarUseLastGoodRotationDuringCorrection->GetInt() + && (bOrientRotationToMovement || bUseControllerDesiredRotation) + && (!OptionalRotation.IsSet() && ClientData->LastAckedMove.IsValid())) + { + OptionalRotation = ClientData->LastAckedMove->SavedRotation; + }*/ + + // #TODO: Currently epic isn't using the gravity direction in corrections, which should be incorrect + // Trigger event + FVector GravityCorrection = OptionalGravityDirection.IsSet() ? OptionalGravityDirection.GetValue() : DefaultGravityDirection; + OnClientCorrectionReceived(*ClientData, TimeStamp, WorldShiftedNewLocation, NewVelocity, NewBase, NewBaseBoneName, bHasBase, bBaseRelativePosition, ServerMovementMode, GravityCorrection); + + // Trust the server's positioning. + if (UpdatedComponent) + { + // Orient to new gravity value + SetCharacterToNewGravity(GravityCorrection, bAutoOrientToFloorNormal); + + // #TODO: Epics 5.1 rotation enforce, we use our own currently + /*if (OptionalRotation.IsSet()) + { + UpdatedComponent->SetWorldLocationAndRotation(WorldShiftedNewLocation, OptionalRotation.GetValue(), false, nullptr, ETeleportType::TeleportPhysics); + } + else + { + UpdatedComponent->SetWorldLocation(WorldShiftedNewLocation, false, nullptr, ETeleportType::TeleportPhysics); + }*/ + + if (bRunClientCorrectionToHMD && BaseVRCharacterOwner) + { + if (OptionalRotation.IsSet()) + { + BaseVRCharacterOwner->SetActorLocationAndRotationVR(WorldShiftedNewLocation, OptionalRotation.GetValue(), false, false, true, true); + //BaseVRCharacterOwner->SetActorRotation(OptionalRotation.GetValue(), ETeleportType::TeleportPhysics); + + // Trust the server's control yaw + if (ClientData->LastAckedMove.IsValid() && !ClientData->LastAckedMove->SavedControlRotation.Equals(OptionalRotation.GetValue())) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->bUseControllerRotationYaw) + { + AController* myController = BaseVRCharacterOwner->GetController(); + if (myController) + { + //FRotator newRot = myController->GetControlRotation(); + myController->SetControlRotation(OptionalRotation.GetValue());//(ClientData->LastAckedMove->SavedControlRotation); + } + } + } + } + + } + else + { + BaseVRCharacterOwner->SetActorLocationVR(WorldShiftedNewLocation, true); + } + } + else + { + if (OptionalRotation.IsSet()) + { + UpdatedComponent->SetWorldLocationAndRotation(WorldShiftedNewLocation, OptionalRotation.GetValue(), false, nullptr, ETeleportType::TeleportPhysics); + + // Trust the server's control yaw + if (ClientData->LastAckedMove.IsValid() && !ClientData->LastAckedMove->SavedControlRotation.Equals(OptionalRotation.GetValue())) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->bUseControllerRotationYaw) + { + AController* myController = BaseVRCharacterOwner->GetController(); + if (myController) + { + //FRotator newRot = myController->GetControlRotation(); + myController->SetControlRotation(OptionalRotation.GetValue());//(ClientData->LastAckedMove->SavedControlRotation); + } + } + } + } + } + else + { + UpdatedComponent->SetWorldLocation(WorldShiftedNewLocation, false, nullptr, ETeleportType::TeleportPhysics); + } + } + } + + Velocity = NewVelocity; + + // Trust the server's movement mode + UPrimitiveComponent* PreviousBase = CharacterOwner->GetMovementBase(); + ApplyNetworkMovementMode(ServerMovementMode); + + // Set base component + UPrimitiveComponent* FinalBase = NewBase; + FName FinalBaseBoneName = NewBaseBoneName; + if (bUnresolvedBase) + { + check(NewBase == NULL); + check(!bBaseRelativePosition); + + // We had an unresolved base from the server + // If walking, we'd like to continue walking if possible, to avoid falling for a frame, so try to find a base where we moved to. + if (PreviousBase && UpdatedComponent) + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false); + if (CurrentFloor.IsWalkableFloor()) + { + FinalBase = CurrentFloor.HitResult.Component.Get(); + FinalBaseBoneName = CurrentFloor.HitResult.BoneName; + } + else + { + FinalBase = nullptr; + FinalBaseBoneName = NAME_None; + } + + /*if (bAutoOrientToFloorNormal && CurrentFloor.IsWalkableFloor()) + { + // Auto Align to the new floor normal + // Set gravity direction to the new floor normal, snap to new rotation on correction + // #TODO: Might need to entirely remove this and let the correction set everything? + AutoTraceAndSetCharacterToNewGravity(CurrentFloor.HitResult, 0.0f); + }*/ + } + } + SetBase(FinalBase, FinalBaseBoneName); + + // Update floor at new location + UpdateFloorFromAdjustment(); + bJustTeleported = true; + + // Even if base has not changed, we need to recompute the relative offsets (since we've moved). + SaveBaseLocation(); + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; + + UpdateComponentVelocity(); + ClientData->bUpdatePosition = true; +} + +bool UVRCharacterMovementComponent::ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& ClientWorldLocation, float ClientYaw, const FVector& RelativeClientLocation, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode) +{ + // Check location difference against global setting + if (!bIgnoreClientMovementErrorChecksAndCorrection) + { + //const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation; + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnAnyThread() == 1) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation; + FString AdjustedDebugString = FString::Printf(TEXT("ServerCheckClientError LocDiff(%.1f) ExceedsAllowablePositionError(%d) TimeStamp(%f)"), + LocDiff.Size(), GetDefault()->ExceedsAllowablePositionError(LocDiff), ClientTimeStamp); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } +#endif + + if (ServerExceedsAllowablePositionError(ClientTimeStamp, DeltaTime, Accel, ClientWorldLocation, RelativeClientLocation, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)) + { + return true; + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + static const auto CVarNetForceClientAdjustmentPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientAdjustmentPercent")); + if (CVarNetForceClientAdjustmentPercent->GetFloat() > UE_SMALL_NUMBER) + { + if (RandomStream.FRand() < CVarNetForceClientAdjustmentPercent->GetFloat()) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("** ServerCheckClientError forced by p.NetForceClientAdjustmentPercent")); + return true; + } + } +#endif + } + else + { +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + if (CVarNetShowCorrections->GetInt() != 0) + { + UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: %s is set to ignore error checks and corrections."), *GetNameSafe(CharacterOwner)); + } +#endif // !UE_BUILD_SHIPPING + } + + // If we are rolling back client rotation + if (!bUseClientControlRotation && !FMath::IsNearlyEqual(FRotator::ClampAxis(ClientYaw), FRotator::ClampAxis(UpdatedComponent->GetComponentRotation().Yaw), CharacterMovementComponentStatics::fRotationCorrectionThreshold)) + { + return true; + } + + return false; +} + +void UVRCharacterMovementComponent::ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& RelativeClientLoc, float ClientYaw, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode) +{ + if (!ShouldUsePackedMovementRPCs()) + { + if (RelativeClientLoc == FVector(1.f, 2.f, 3.f)) // first part of double servermove + { + return; + } + } + + FNetworkPredictionData_Server_VRCharacter* ServerData = ((FNetworkPredictionData_Server_VRCharacter*)GetPredictionData_Server_Character()); + check(ServerData); + + // Don't prevent more recent updates from being sent if received this frame. + // We're going to send out an update anyway, might as well be the most recent one. + APlayerController* PC = Cast(CharacterOwner->GetController()); + if ((ServerData->LastUpdateTime != GetWorld()->TimeSeconds)) + { + const AGameNetworkManager* GameNetworkManager = (const AGameNetworkManager*)(AGameNetworkManager::StaticClass()->GetDefaultObject()); + if (GameNetworkManager->WithinUpdateDelayBounds(PC, ServerData->LastUpdateTime)) + { + return; + } + } + + // Offset may be relative to base component + FVector ClientLoc = RelativeClientLoc; + if (MovementBaseUtility::UseRelativeLocation(ClientMovementBase)) + { + MovementBaseUtility::TransformLocationToWorld(ClientMovementBase, ClientBaseBoneName, RelativeClientLoc, ClientLoc); + } + else + { + ClientLoc = FRepMovement::RebaseOntoLocalOrigin(ClientLoc, this); + } + + FVector ServerLoc = UpdatedComponent->GetComponentLocation(); + + // Client may send a null movement base when walking on bases with no relative location (to save bandwidth). + // In this case don't check movement base in error conditions, use the server one (which avoids an error based on differing bases). Position will still be validated. + if (ClientMovementBase == nullptr) + { + TEnumAsByte NetMovementMode(MOVE_None); + TEnumAsByte NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ClientMovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + if (NetMovementMode == MOVE_Walking) + { + ClientMovementBase = CharacterOwner->GetBasedMovement().MovementBase; + ClientBaseBoneName = CharacterOwner->GetBasedMovement().BoneName; + } + } + + // If base location is out of sync on server and client, changing base can result in a jarring correction. + // So in the case that the base has just changed on server or client, server trusts the client (within a threshold) + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + FName MovementBaseBoneName = CharacterOwner->GetBasedMovement().BoneName; + const bool bServerIsFalling = IsFalling(); + const bool bClientIsFalling = ClientMovementMode == MOVE_Falling; + const bool bServerJustLanded = bLastServerIsFalling && !bServerIsFalling; + const bool bClientJustLanded = bLastClientIsFalling && !bClientIsFalling; + + FVector RelativeLocation = ServerLoc; + FVector RelativeVelocity = Velocity; + bool bUseLastBase = false; + bool bFallingWithinAcceptableError = false; + + // Potentially trust the client a little when landing + static const auto CVarClientAuthorityThresholdOnBaseChange = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ClientAuthorityThresholdOnBaseChange")); + static const auto CVarMaxFallingCorrectionLeash = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxFallingCorrectionLeash")); + + const float ClientAuthorityThreshold = CVarClientAuthorityThresholdOnBaseChange->GetFloat(); + const float MaxFallingCorrectionLeash = CVarMaxFallingCorrectionLeash->GetFloat(); + const bool bDeferServerCorrectionsWhenFalling = ClientAuthorityThreshold > 0.f || MaxFallingCorrectionLeash > 0.f; + if (bDeferServerCorrectionsWhenFalling) + { + // Teleports and other movement modes mean we should just trust the server like we normally would + if (bTeleportedSinceLastUpdate || (MovementMode != MOVE_Walking && MovementMode != MOVE_Falling)) + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + // MaxFallingCorrectionLeash indicates we'll use a variable correction size based on the error on take-off and the direction of movement. + // ClientAuthorityThreshold is an static client-trusting correction upon landing. + // If both are set, use the smaller of the two. If only one is set, use that. If neither are set, we wouldn't even be inside this block. + float MaxLandingCorrection = 0.f; + if (ClientAuthorityThreshold > 0.f && MaxFallingCorrectionLeash > 0.f) + { + MaxLandingCorrection = FMath::Min(ClientAuthorityThreshold, MaxServerClientErrorWhileFalling); + } + else + { + MaxLandingCorrection = FMath::Max(ClientAuthorityThreshold, MaxServerClientErrorWhileFalling); + } + + if (bCanTrustClientOnLanding && MaxLandingCorrection > 0.f && (bClientJustLanded || bServerJustLanded)) + { + // no longer falling; server should trust client up to a point to finish the landing as the client sees it + const FVector LocDiff = ServerLoc - ClientLoc; + + if (!LocDiff.IsNearlyZero(UE_KINDA_SMALL_NUMBER)) + { + if (LocDiff.SizeSquared() < FMath::Square(MaxLandingCorrection)) + { + ServerLoc = ClientLoc; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + else + { + const FVector ClampedDiff = LocDiff.GetSafeNormal() * MaxLandingCorrection; + ServerLoc -= ClampedDiff; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + } + + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + if (bServerIsFalling && bLastServerIsWalking && !bTeleportedSinceLastUpdate) + { + float ClientForwardFactor = 1.f; + UPrimitiveComponent* LastServerMovementBaseVRPtr = LastServerMovementBaseVR.Get(); + if (IsValid(LastServerMovementBaseVRPtr) && MovementBaseUtility::IsDynamicBase(LastServerMovementBaseVRPtr) && MaxWalkSpeed > UE_KINDA_SMALL_NUMBER) + { + const FVector LastBaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(LastServerMovementBaseVRPtr, LastServerMovementBaseBoneName); + RelativeVelocity = Velocity - LastBaseVelocity; + const FVector BaseDirection = LastBaseVelocity.GetSafeNormal2D(); + const FVector RelativeDirection = RelativeVelocity * (1.f / MaxWalkSpeed); + + ClientForwardFactor = FMath::Clamp(FVector::DotProduct(BaseDirection, RelativeDirection), 0.f, 1.f); + + // To improve position syncing, use old base for take-off + if (MovementBaseUtility::UseRelativeLocation(LastServerMovementBaseVRPtr)) + { + // Relative Location + MovementBaseUtility::TransformLocationToLocal(LastServerMovementBaseVRPtr, LastServerMovementBaseBoneName, UpdatedComponent->GetComponentLocation(), RelativeLocation); + bUseLastBase = true; + } + } + + if (ClientAuthorityThreshold > 0.f && ClientForwardFactor < 1.f) + { + const float AdjustedClientAuthorityThreshold = ClientAuthorityThreshold * (1.f - ClientForwardFactor); + const FVector LocDiff = ServerLoc - ClientLoc; + + // Potentially trust the client a little when taking off in the opposite direction to the base (to help not get corrected back onto the base) + if (!LocDiff.IsNearlyZero(UE_KINDA_SMALL_NUMBER)) + { + if (LocDiff.SizeSquared() < FMath::Square(AdjustedClientAuthorityThreshold)) + { + ServerLoc = ClientLoc; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + else + { + const FVector ClampedDiff = LocDiff.GetSafeNormal() * AdjustedClientAuthorityThreshold; + ServerLoc -= ClampedDiff; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + } + } + + if (ClientForwardFactor < 1.f) + { + MaxServerClientErrorWhileFalling = FMath::Min((ServerLoc - ClientLoc).Size() * (1.f - ClientForwardFactor), MaxFallingCorrectionLeash); + bCanTrustClientOnLanding = true; + } + else + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + } + else if (!bServerIsFalling && bCanTrustClientOnLanding) + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + if (MaxServerClientErrorWhileFalling > 0.f && (bServerIsFalling || bClientIsFalling)) + { + const FVector LocDiff = ServerLoc - ClientLoc; + if (LocDiff.SizeSquared() <= FMath::Square(MaxServerClientErrorWhileFalling)) + { + ServerLoc = ClientLoc; + // Still want a velocity update when we first take off + bFallingWithinAcceptableError = true; + } + else + { + // Change ServerLoc to be on the edge of the acceptable error rather than doing a full correction. + // This is not actually changing the server position, but changing it as far as corrections are concerned. + // This means we're just holding the client on a longer leash while we're falling. + static const auto CVarMaxFallingCorrectionLeashBuffer = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxFallingCorrectionLeashBuffer")); + ServerLoc = ServerLoc - LocDiff.GetSafeNormal() * FMath::Clamp(MaxServerClientErrorWhileFalling - CVarMaxFallingCorrectionLeashBuffer->GetFloat(), 0.f, MaxServerClientErrorWhileFalling); + } + } + } + + bool bInClientAuthoritativeMovementMode = false; + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + + TEnumAsByte NetMovementMode(MOVE_None); + TEnumAsByte NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ClientMovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + if (NetMovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + bInClientAuthoritativeMovementMode = true; + } + + // Compute the client error from the server's position + // If client has accumulated a noticeable positional error, correct them. + bNetworkLargeClientCorrection = ServerData->bForceClientUpdate; + if (!bInClientAuthoritativeMovementMode && (ServerData->bForceClientUpdate || (!bFallingWithinAcceptableError && ServerCheckClientErrorVR(ClientTimeStamp, DeltaTime, Accel, ClientLoc, ClientYaw, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)))) + { + //UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + ServerData->PendingAdjustment.NewVel = Velocity; + ServerData->PendingAdjustment.NewBase = MovementBase; + ServerData->PendingAdjustment.NewBaseBoneName = MovementBaseBoneName; + ServerData->PendingAdjustment.NewRot = UpdatedComponent->GetComponentRotation(); + ServerData->PendingAdjustment.GravityDirection = GetGravityDirection(); + + if (bRunClientCorrectionToHMD && IsValid(BaseVRCharacterOwner)) + { + FVector CapsuleLoc = BaseVRCharacterOwner->GetVRLocation(); + CapsuleLoc.Z = ServerLoc.Z; + ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(CapsuleLoc, this); + //ServerData->PendingAdjustment.NewRot = BaseVRCharacterOwner->GetVRRotation(); + } + else + { + ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(ServerLoc, this); + //ServerData->PendingAdjustment.NewRot = UpdatedComponent->GetComponentRotation(); + } + + ServerData->PendingAdjustment.bBaseRelativePosition = (bDeferServerCorrectionsWhenFalling && bUseLastBase) || MovementBaseUtility::UseRelativeLocation(MovementBase); + ServerData->PendingAdjustment.bBaseRelativeVelocity = false; + + // Relative location? + if (ServerData->PendingAdjustment.bBaseRelativePosition) + { + if (bDeferServerCorrectionsWhenFalling && bUseLastBase) + { + ServerData->PendingAdjustment.NewVel = RelativeVelocity; + ServerData->PendingAdjustment.NewBase = LastServerMovementBaseVR.Get(); + ServerData->PendingAdjustment.NewBaseBoneName = LastServerMovementBaseBoneName; + + if (bRunClientCorrectionToHMD && IsValid(BaseVRCharacterOwner)) + { + FBasedMovementInfo BaseInfo = CharacterOwner->GetBasedMovement(); + FVector BaseLocation; + FQuat BaseQuat; + + const bool bResult = MovementBaseUtility::GetMovementBaseTransform(BaseInfo.MovementBase, BaseInfo.BoneName, BaseLocation, BaseQuat); + if (bResult) + { + ServerData->PendingAdjustment.NewLoc = FTransform(BaseQuat, BaseLocation).InverseTransformPositionNoScale(CharacterOwner->GetBasedMovement().Location); + } + else + { + ServerData->PendingAdjustment.NewLoc = RelativeLocation; + } + } + else + { + ServerData->PendingAdjustment.NewLoc = RelativeLocation; + } + } + else + { + if (bRunClientCorrectionToHMD && IsValid(BaseVRCharacterOwner)) + { + ServerData->PendingAdjustment.NewLoc = CharacterOwner->GetMovementBase()->GetComponentTransform().InverseTransformPosition(ServerData->PendingAdjustment.NewLoc); + } + else + { + ServerData->PendingAdjustment.NewLoc = CharacterOwner->GetBasedMovement().Location; + } + + static const auto CVarNetUseBaseRelativeVelocity = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetUseBaseRelativeVelocity")); + if (CVarNetUseBaseRelativeVelocity->GetInt()) + { + // Store world velocity converted to local space of movement base + ServerData->PendingAdjustment.bBaseRelativeVelocity = true; + const FVector CurrentVelocity = ServerData->PendingAdjustment.NewVel; + MovementBaseUtility::TransformDirectionToLocal(MovementBase, MovementBaseBoneName, CurrentVelocity, ServerData->PendingAdjustment.NewVel); + } + } + + // TODO: this could be a relative rotation, but all client corrections ignore rotation right now except the root motion one, which would need to be updated. + //ServerData->PendingAdjustment.NewRot = CharacterOwner->GetBasedMovement().Rotation; + } + +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime")); + if (CVarNetShowCorrections->GetInt() != 0) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc; + const FString BaseString = MovementBase ? MovementBase->GetPathName(MovementBase->GetOutermost()) : TEXT("None"); + UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: Error for %s at Time=%.3f is %3.3f LocDiff(%s) ClientLoc(%s) ServerLoc(%s) Base: %s Bone: %s Accel(%s) Velocity(%s)"), + *GetNameSafe(CharacterOwner), ClientTimeStamp, LocDiff.Size(), *LocDiff.ToString(), *ClientLoc.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *BaseString, *ServerData->PendingAdjustment.NewBaseBoneName.ToString(), *Accel.ToString(), *Velocity.ToString()); + const float DebugLifetime = CVarNetCorrectionLifetime->GetFloat(); + DrawDebugCapsule(GetWorld(), UpdatedComponent->GetComponentLocation(), CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(100, 255, 100), false, DebugLifetime); + DrawDebugCapsule(GetWorld(), ClientLoc, CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(255, 100, 100), false, DebugLifetime); + } +#endif + + ServerData->LastUpdateTime = GetWorld()->TimeSeconds; + ServerData->PendingAdjustment.DeltaTime = DeltaTime; + ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp; + ServerData->PendingAdjustment.bAckGoodMove = false; + ServerData->PendingAdjustment.MovementMode = PackNetworkMovementMode(); + + //PerfCountersIncrement(PerfCounter_NumServerMoveCorrections); + } + else + { + if (bInClientAuthoritativeMovementMode || ServerShouldUseAuthoritativePosition(ClientTimeStamp, DeltaTime, Accel, ClientLoc, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc; //-V595 + if (!LocDiff.IsZero() || ClientMovementMode != PackNetworkMovementMode() || GetMovementBase() != ClientMovementBase || (CharacterOwner && CharacterOwner->GetBasedMovement().BoneName != ClientBaseBoneName)) + { + // Just set the position. On subsequent moves we will resolve initially overlapping conditions. + UpdatedComponent->SetWorldLocation(ClientLoc, false); //-V595 + + // Trust the client's movement mode. + ApplyNetworkMovementMode(ClientMovementMode); + + // Update base and floor at new location. + SetBase(ClientMovementBase, ClientBaseBoneName); + UpdateFloorFromAdjustment(); + + // Even if base has not changed, we need to recompute the relative offsets (since we've moved). + SaveBaseLocation(); + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; + } + } + + // acknowledge receipt of this successful servermove() + ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp; + ServerData->PendingAdjustment.bAckGoodMove = true; + } + + //PerfCountersIncrement(PerfCounter_NumServerMoves); + + ServerData->bForceClientUpdate = false; + + LastServerMovementBaseVR = MovementBase; + LastServerMovementBaseBoneName = MovementBaseBoneName; + bLastClientIsFalling = bClientIsFalling; + bLastServerIsFalling = bServerIsFalling; + bLastServerIsWalking = MovementMode == MOVE_Walking; +} + +bool UVRCharacterMovementComponent::ClientUpdatePositionAfterServerUpdate() +{ + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementClientUpdatePositionAfterServerUpdate); + if (!HasValidData()) + { + return false; + } + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + check(ClientData); + + if (!ClientData->bUpdatePosition) + { + return false; + } + + ClientData->bUpdatePosition = false; + + // Don't do any network position updates on things running PHYS_RigidBody + if (CharacterOwner->GetRootComponent() && CharacterOwner->GetRootComponent()->IsSimulatingPhysics()) + { + return false; + } + + if (ClientData->SavedMoves.Num() == 0) + { + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientUpdatePositionAfterServerUpdate No saved moves to replay"), ClientData->SavedMoves.Num()); + + // With no saved moves to resimulate, the move the server updated us with is the last move we've done, no resimulation needed. + CharacterOwner->bClientResimulateRootMotion = false; + if (CharacterOwner->bClientResimulateRootMotionSources) + { + // With no resimulation, we just update our current root motion to what the server sent us + UE_LOG(LogRootMotion, VeryVerbose, TEXT("CurrentRootMotion getting updated to ServerUpdate state: %s"), *CharacterOwner->GetName()); + CurrentRootMotion.UpdateStateFrom(CharacterOwner->SavedRootMotion); + CharacterOwner->bClientResimulateRootMotionSources = false; + } + CharacterOwner->SavedRootMotion.Clear(); + + return false; + } + + // Save important values that might get affected by the replay. + const float SavedAnalogInputModifier = AnalogInputModifier; + const FRootMotionMovementParams BackupRootMotionParams = RootMotionParams; // For animation root motion + const FRootMotionSourceGroup BackupRootMotion = CurrentRootMotion; + const bool bRealPressedJump = CharacterOwner->bPressedJump; + const float RealJumpMaxHoldTime = CharacterOwner->JumpMaxHoldTime; + const int32 RealJumpMaxCount = CharacterOwner->JumpMaxCount; + const bool bRealCrouch = bWantsToCrouch; + const bool bRealForceMaxAccel = bForceMaxAccel; + CharacterOwner->bClientWasFalling = (MovementMode == MOVE_Falling); + CharacterOwner->bClientUpdating = true; + bForceNextFloorCheck = true; + + // Store out our custom properties to restore after replaying + const FVRMoveActionArray Orig_MoveActions = MoveActionArray; + const FVector Orig_CustomInput = CustomVRInputVector; + const EVRConjoinedMovementModes Orig_VRReplicatedMovementMode = VRReplicatedMovementMode; + const FVector Orig_RequestedVelocity = RequestedVelocity; + const bool Orig_HasRequestedVelocity = HasRequestedVelocity(); + + // Store our VRchar custom properties + FVector Orig_CameraLoc = VRRootCapsule->curCameraLoc; + FRotator Orig_curCameraRot = VRRootCapsule->curCameraRot; + FVector Orig_DifferenceFromLastFrame = VRRootCapsule->DifferenceFromLastFrame; + FVector Orig_AdditionalVRInputVector = AdditionalVRInputVector; + FRotator Orig_CameraRotOffset = VRRootCapsule->StoredCameraRotOffset; + + // Replay moves that have not yet been acked. + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientUpdatePositionAfterServerUpdate Replaying %d Moves, starting at Timestamp %f"), ClientData->SavedMoves.Num(), ClientData->SavedMoves[0]->TimeStamp); + for (int32 i = 0; i < ClientData->SavedMoves.Num(); i++) + { + FSavedMove_Character* const CurrentMove = ClientData->SavedMoves[i].Get(); + checkSlow(CurrentMove != nullptr); + + // Make current SavedMove accessible to any functions that might need it.PrepMoveFor + SetCurrentReplayedSavedMove(CurrentMove); + + CurrentMove->PrepMoveFor(CharacterOwner); + + if (ShouldUsePackedMovementRPCs()) + { + // Make current move data accessible to MoveAutonomous or any other functions that might need it. + if (FCharacterNetworkMoveData* NewMove = GetNetworkMoveDataContainer().GetNewMoveData()) + { + SetCurrentNetworkMoveData(NewMove); + NewMove->ClientFillNetworkMoveData(*CurrentMove, FCharacterNetworkMoveData::ENetworkMoveType::NewMove); + } + } + + MoveAutonomous(CurrentMove->TimeStamp, CurrentMove->DeltaTime, CurrentMove->GetCompressedFlags(), CurrentMove->Acceleration); + + CurrentMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Replay); + SetCurrentNetworkMoveData(nullptr); + SetCurrentReplayedSavedMove(nullptr); + } + const bool bPostReplayPressedJump = CharacterOwner->bPressedJump; + + if (FSavedMove_Character* const PendingMove = ClientData->PendingMove.Get()) + { + PendingMove->bForceNoCombine = true; + } + + // Restore saved values. + AnalogInputModifier = SavedAnalogInputModifier; + RootMotionParams = BackupRootMotionParams; + CurrentRootMotion = BackupRootMotion; + if (CharacterOwner->bClientResimulateRootMotionSources) + { + // If we were resimulating root motion sources, it's because we had mismatched state + // with the server - we just resimulated our SavedMoves and now need to restore + // CurrentRootMotion with the latest "good state" + UE_LOG(LogRootMotion, VeryVerbose, TEXT("CurrentRootMotion getting updated after ServerUpdate replays: %s"), *CharacterOwner->GetName()); + CurrentRootMotion.UpdateStateFrom(CharacterOwner->SavedRootMotion); + CharacterOwner->bClientResimulateRootMotionSources = false; + } + + CharacterOwner->SavedRootMotion.Clear(); + CharacterOwner->bClientResimulateRootMotion = false; + CharacterOwner->bClientUpdating = false; + CharacterOwner->bPressedJump = bRealPressedJump || bPostReplayPressedJump; + CharacterOwner->JumpMaxHoldTime = RealJumpMaxHoldTime; + CharacterOwner->JumpMaxCount = RealJumpMaxCount; + bWantsToCrouch = bRealCrouch; + bForceMaxAccel = bRealForceMaxAccel; + bForceNextFloorCheck = true; + + // Restore our custom properties + MoveActionArray = Orig_MoveActions; + CustomVRInputVector = Orig_CustomInput; + VRReplicatedMovementMode = Orig_VRReplicatedMovementMode; + RequestedVelocity = Orig_RequestedVelocity; + SetHasRequestedVelocity(Orig_HasRequestedVelocity); + + // Restore our VRchar custom properties + VRRootCapsule->curCameraLoc = Orig_CameraLoc; + VRRootCapsule->curCameraRot = Orig_curCameraRot; + VRRootCapsule->DifferenceFromLastFrame = Orig_DifferenceFromLastFrame; + AdditionalVRInputVector = Orig_AdditionalVRInputVector; + VRRootCapsule->StoredCameraRotOffset = Orig_CameraRotOffset; + VRRootCapsule->GenerateOffsetToWorld(false, false); + + return (ClientData->SavedMoves.Num() > 0); +} + +FVector UVRCharacterMovementComponent::GetPenetrationAdjustment(const FHitResult& Hit) const +{ + // This checks for a walking collision override on the penetrated object + // If found then it stops penetration adjustments. + if (MovementMode == EMovementMode::MOVE_Walking && VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && Hit.Component.IsValid()) + { + ECollisionResponse WalkingResponse; + WalkingResponse = Hit.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride); + + if (WalkingResponse == ECR_Ignore || WalkingResponse == ECR_Overlap) + { + return FVector::ZeroVector; + } + } + + FVector Result = Super::GetPenetrationAdjustment(Hit); + + if (CharacterOwner) + { + const bool bIsProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + float MaxDistance = bIsProxy ? MaxDepenetrationWithGeometryAsProxy : MaxDepenetrationWithGeometry; + if (Hit.HitObjectHandle.DoesRepresentClass(APawn::StaticClass())) + { + MaxDistance = bIsProxy ? MaxDepenetrationWithPawnAsProxy : MaxDepenetrationWithPawn; + } + + Result = Result.GetClampedToMaxSize(MaxDistance); + } + + return Result; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp new file mode 100644 index 0000000..c634b3b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp @@ -0,0 +1,784 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "VRExpansionFunctionLibrary.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRExpansionFunctionLibrary) + +#include "DrawDebugHelpers.h" +#include "Engine/Engine.h" +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" +#include "InputCoreTypes.h" +#include "Grippables/HandSocketComponent.h" +#include "Misc/CollisionIgnoreSubsystem.h" +#include "Components/SplineComponent.h" +#include "Components/SplineMeshComponent.h" +#include "Components/PrimitiveComponent.h" +#include "GripMotionControllerComponent.h" +//#include "IMotionController.h" +//#include "HeadMountedDisplayFunctionLibrary.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GameplayTagContainer.h" +#include "XRMotionControllerBase.h" +//#include "IHeadMountedDisplay.h" + +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/ParticleHandle.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" + +#if WITH_EDITOR +#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h" +#endif + +//General Log +DEFINE_LOG_CATEGORY(VRExpansionFunctionLibraryLog); + +UGameViewportClient* UVRExpansionFunctionLibrary::GetGameViewportClient(UObject* WorldContextObject) +{ + if (WorldContextObject) + { + return WorldContextObject->GetWorld()->GetGameViewport(); + } + + return nullptr; +} + +void UVRExpansionFunctionLibrary::SetActorsIgnoreAllCollision(UObject* WorldContextObject, AActor* Actor1, AActor* Actor2, bool bIgnoreCollision) +{ + TInlineComponentArray PrimitiveComponents1; + Actor1->GetComponents(PrimitiveComponents1); + + TInlineComponentArray PrimitiveComponents2; + Actor2->GetComponents(PrimitiveComponents2); + + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem) + { + // Just a temp flag to only check state on the first component pair + bool bIgnorefirst = true; + for (int i = 0; i < PrimitiveComponents1.Num(); ++i) + { + for (int j = 0; j < PrimitiveComponents2.Num(); ++j) + { + + // Don't ignore collision if one is already invalid + if (!IsValid(PrimitiveComponents1[i]) || !IsValid(PrimitiveComponents2[j])) + { + continue; + } + + // Don't ignore collision if no collision on at least one of the objects + if (PrimitiveComponents1[i]->GetCollisionEnabled() == ECollisionEnabled::NoCollision || PrimitiveComponents2[j]->GetCollisionEnabled() == ECollisionEnabled::NoCollision) + { + if (!bIgnoreCollision) + { + if (CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(PrimitiveComponents1[i], PrimitiveComponents2[j])) + { + UE_LOG(VRExpansionFunctionLibraryLog, Error, TEXT("Set Actors Ignore Collision called with at least one object set to no collision that are ignoring collision already!! %s, %s"),*PrimitiveComponents1[i]->GetName(), *PrimitiveComponents2[j]->GetName()); + } + } + continue; + } + + CollisionIgnoreSubsystem->SetComponentCollisionIgnoreState(true, true, PrimitiveComponents1[i], NAME_None, PrimitiveComponents2[j], NAME_None, bIgnoreCollision, bIgnorefirst); + bIgnorefirst = false; + } + } + } +} + +void UVRExpansionFunctionLibrary::SetObjectsIgnoreCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1, FName OptionalBoneName1, bool bAddChildBones1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bAddChildBones2, bool bIgnoreCollision) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem) + { + CollisionIgnoreSubsystem->SetComponentCollisionIgnoreState(bAddChildBones1, bAddChildBones2, Prim1, OptionalBoneName1, Prim2, OptionalBoneName2, bIgnoreCollision, true); + } +} + + +bool UVRExpansionFunctionLibrary::IsComponentIgnoringCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem) + { + return CollisionIgnoreSubsystem->IsComponentIgnoringCollision(Prim1); + } + + return false; +} + +bool UVRExpansionFunctionLibrary::AreComponentsIgnoringCollisions(UObject* WorldContextObject, UPrimitiveComponent* Prim1, UPrimitiveComponent * Prim2) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem && CollisionIgnoreSubsystem->CollisionTrackedPairs.Num() > 0) + { + return CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(Prim1, Prim2); + } + + return false; +} + +void UVRExpansionFunctionLibrary::RemoveObjectCollisionIgnore(UObject* WorldContextObject, UPrimitiveComponent* Prim1) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem) + { + CollisionIgnoreSubsystem->RemoveComponentCollisionIgnoreState(Prim1); + } +} + +void UVRExpansionFunctionLibrary::RemoveActorCollisionIgnore(UObject* WorldContextObject, AActor* Actor1) +{ + TInlineComponentArray PrimitiveComponents1; + Actor1->GetComponents(PrimitiveComponents1); + + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem(); + + if (CollisionIgnoreSubsystem) + { + for (int i = 0; i < PrimitiveComponents1.Num(); ++i) + { + CollisionIgnoreSubsystem->RemoveComponentCollisionIgnoreState(PrimitiveComponents1[i]); + } + } +} + +void UVRExpansionFunctionLibrary::LowPassFilter_RollingAverage(FVector lastAverage, FVector newSample, FVector& newAverage, int32 numSamples) +{ + newAverage = lastAverage; + newAverage -= newAverage / numSamples; + newAverage += newSample / numSamples; +} + +void UVRExpansionFunctionLibrary::LowPassFilter_Exponential(FVector lastAverage, FVector newSample, FVector& newAverage, float sampleFactor) +{ + newAverage = (newSample * sampleFactor) + ((1 - sampleFactor) * lastAverage); +} + +bool UVRExpansionFunctionLibrary::GetIsActorMovable(AActor* ActorToCheck) +{ + if (!ActorToCheck) + return false; + + if (USceneComponent* rootComp = ActorToCheck->GetRootComponent()) + { + return rootComp->Mobility == EComponentMobility::Movable; + } + + return false; +} + +void UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(FName SlotType, AActor* Actor, FVector WorldLocation, float MaxRange, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* QueryController) +{ + bHadSlotInRange = false; + SlotWorldTransform = FTransform::Identity; + SlotName = NAME_None; + UHandSocketComponent* TargetHandSocket = nullptr; + + if (!Actor) + return; + + if (USceneComponent* rootComp = Actor->GetRootComponent()) + { + GetGripSlotInRangeByTypeName_Component(SlotType, rootComp, WorldLocation, MaxRange, bHadSlotInRange, SlotWorldTransform, SlotName, QueryController); + } +} + +void UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(FName SlotType, USceneComponent* Component, FVector WorldLocation, float MaxRange, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* QueryController) +{ + bHadSlotInRange = false; + SlotWorldTransform = FTransform::Identity; + SlotName = NAME_None; + UHandSocketComponent* TargetHandSocket = nullptr; + + if (!Component) + return; + + FVector RelativeWorldLocation = Component->GetComponentTransform().InverseTransformPosition(WorldLocation); + MaxRange = FMath::Square(MaxRange); + + float ClosestSlotDistance = -0.1f; + + TArray SocketNames = Component->GetAllSocketNames(); + + FString GripIdentifier = SlotType.ToString(); + + int foundIndex = 0; + + for (int i = 0; i < SocketNames.Num(); ++i) + { + if (SocketNames[i].ToString().Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + float vecLen = FVector::DistSquared(RelativeWorldLocation, Component->GetSocketTransform(SocketNames[i], ERelativeTransformSpace::RTS_Component).GetLocation()); + + if (MaxRange >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance)) + { + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + foundIndex = i; + } + } + } + + TArray AttachChildren = Component->GetAttachChildren(); + + TArray RotationallyMatchingHandSockets; + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA()) + { + if (UHandSocketComponent* SocketComp = Cast(AttachChild)) + { + if (SocketComp->bDisabled) + continue; + + FName BoneName = SocketComp->GetAttachSocketName(); + FString SlotPrefix = BoneName != NAME_None ? BoneName.ToString() + SocketComp->SlotPrefix.ToString() : SocketComp->SlotPrefix.ToString(); + + if (SlotPrefix.Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + FVector SocketRelativeLocation = Component->GetComponentTransform().InverseTransformPosition(SocketComp->GetHandSocketTransform(QueryController, true).GetLocation()); + float vecLen = FVector::DistSquared(RelativeWorldLocation, SocketRelativeLocation); + //float vecLen = FVector::DistSquared(RelativeWorldLocation, SocketComp->GetRelativeLocation()); + if (SocketComp->bAlwaysInRange) + { + if (SocketComp->bMatchRotation) + { + RotationallyMatchingHandSockets.Add(SocketComp); + } + else + { + TargetHandSocket = SocketComp; + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + } + } + else + { + float RangeVal = (SocketComp->OverrideDistance > 0.0f ? FMath::Square(SocketComp->OverrideDistance) : MaxRange); + if (RangeVal >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance)) + { + if (SocketComp->bMatchRotation) + { + RotationallyMatchingHandSockets.Add(SocketComp); + } + else + { + TargetHandSocket = SocketComp; + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + } + } + } + } + } + } + } + + // Try and sort through any hand sockets flagged as rotationally matched + if (RotationallyMatchingHandSockets.Num() > 0) + { + FQuat ControllerRot = QueryController->GetPivotTransform().GetRotation(); + //FQuat ClosestQuat = RotationallyMatchingHandSockets[0]->GetComponentTransform().GetRotation(); + FQuat ClosestQuat = RotationallyMatchingHandSockets[0]->GetHandSocketTransform(QueryController, true).GetRotation(); + + TargetHandSocket = RotationallyMatchingHandSockets[0]; + bHadSlotInRange = true; + ClosestSlotDistance = ControllerRot.AngularDistance(ClosestQuat); + for (int i = 1; i < RotationallyMatchingHandSockets.Num(); i++) + { + //float CheckDistance = ControllerRot.AngularDistance(RotationallyMatchingHandSockets[i]->GetComponentTransform().GetRotation()); + float CheckDistance = ControllerRot.AngularDistance(RotationallyMatchingHandSockets[i]->GetHandSocketTransform(QueryController, true).GetRotation()); + if (CheckDistance < ClosestSlotDistance) + { + TargetHandSocket = RotationallyMatchingHandSockets[i]; + ClosestSlotDistance = CheckDistance; + } + } + } + + if (bHadSlotInRange) + { + if (TargetHandSocket) + { + SlotWorldTransform = TargetHandSocket->GetHandSocketTransform(QueryController); + SlotName = TargetHandSocket->GetFName(); + SlotWorldTransform.SetScale3D(FVector(1.0f)); + } + else + { + SlotWorldTransform = Component->GetSocketTransform(SocketNames[foundIndex]); + SlotName = SocketNames[foundIndex]; + SlotWorldTransform.SetScale3D(FVector(1.0f)); + } + } +} + +bool UVRExpansionFunctionLibrary::GetHandFromMotionSourceName(FName MotionSource, EControllerHand& Hand) +{ + Hand = EControllerHand::Left; + if (FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, Hand)) + { + return true; + } + + return false; +} + +FRotator UVRExpansionFunctionLibrary::GetHMDPureYaw(FRotator HMDRotation) +{ + return GetHMDPureYaw_I(HMDRotation); +} + +EBPHMDWornState UVRExpansionFunctionLibrary::GetIsHMDWorn() +{ + + if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice()) + { + return (EBPHMDWornState)GEngine->XRSystem->GetHMDDevice()->GetHMDWornState(); + } + + return EBPHMDWornState::Unknown; +} + +bool UVRExpansionFunctionLibrary::GetIsHMDConnected() +{ + return GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice() && GEngine->XRSystem->GetHMDDevice()->IsHMDConnected(); +} + +EBPHMDDeviceType UVRExpansionFunctionLibrary::GetHMDType() +{ + if (GEngine && GEngine->XRSystem.IsValid()) + { + /* + if (GEngine && GEngine->XRSystem.IsValid()) + { + Ar.Logf(*GEngine->XRSystem->GetVersionString()); + } + */ + + // #TODO 4.19: Figure out a way to replace this...its broken now + /*IHeadMountedDisplay* HMDDevice = GEngine->XRSystem->GetHMDDevice(); + if (HMDDevice) + { + EHMDDeviceType::Type HMDDeviceType = HMDDevice->GetHMDDeviceType(); + + switch (HMDDeviceType) + { + case EHMDDeviceType::DT_ES2GenericStereoMesh: return EBPHMDDeviceType::DT_ES2GenericStereoMesh; break; + case EHMDDeviceType::DT_GearVR: return EBPHMDDeviceType::DT_GearVR; break; + case EHMDDeviceType::DT_Morpheus: return EBPHMDDeviceType::DT_Morpheus; break; + case EHMDDeviceType::DT_OculusRift: return EBPHMDDeviceType::DT_OculusRift; break; + case EHMDDeviceType::DT_SteamVR: return EBPHMDDeviceType::DT_SteamVR; break; + case EHMDDeviceType::DT_GoogleVR: return EBPHMDDeviceType::DT_GoogleVR; break; + } + + }*/ + + // There are no device type entries for these now.... + // Does the device type go away soon leaving only FNames? + // #TODO: 4.19? + // GearVR doesn't even return anything gut OculusHMD in FName currently. + + static const FName SteamVRSystemName(TEXT("SteamVR")); + static const FName OculusSystemName(TEXT("OculusHMD")); + static const FName PSVRSystemName(TEXT("PSVR")); + static const FName OSVRSystemName(TEXT("OSVR")); + static const FName GoogleARCoreSystemName(TEXT("FGoogleARCoreHMD")); + static const FName AppleARKitSystemName(TEXT("AppleARKit")); + static const FName GoogleVRHMDSystemName(TEXT("FGoogleVRHMD")); + + FName DeviceName(NAME_None); + DeviceName = GEngine->XRSystem->GetSystemName(); + + + if (DeviceName == FName(TEXT("SimpleHMD"))) + return EBPHMDDeviceType::DT_ES2GenericStereoMesh; + else if (DeviceName == SteamVRSystemName) + return EBPHMDDeviceType::DT_SteamVR; + else if (DeviceName == OculusSystemName) + return EBPHMDDeviceType::DT_OculusHMD; + else if (DeviceName == PSVRSystemName) + return EBPHMDDeviceType::DT_PSVR; + else if (DeviceName == OSVRSystemName) + return EBPHMDDeviceType::DT_SteamVR; + else if (DeviceName == GoogleARCoreSystemName) + return EBPHMDDeviceType::DT_GoogleARCore; + else if (DeviceName == AppleARKitSystemName) + return EBPHMDDeviceType::DT_AppleARKit; + else if (DeviceName == GoogleVRHMDSystemName) + return EBPHMDDeviceType::DT_GoogleVR; + } + + // Default to unknown + return EBPHMDDeviceType::DT_Unknown; +} + + +bool UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() +{ +#if WITH_EDITOR + if (GIsEditor) + { + if (UEditorEngine* EdEngine = Cast(GEngine)) + { + TOptional PlayInfo = EdEngine->GetPlayInEditorSessionInfo(); + if (PlayInfo.IsSet()) + { + return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview; + } + else + { + return false; + } + } + } +#endif + + // Is not an editor build, default to true here + return true; +} + +bool UVRExpansionFunctionLibrary::IsInVREditorPreview() +{ +#if WITH_EDITOR + if (GIsEditor) + { + if (UEditorEngine* EdEngine = Cast(GEngine)) + { + TOptional PlayInfo = EdEngine->GetPlayInEditorSessionInfo(); + if (PlayInfo.IsSet()) + { + return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview; + } + else + { + return false; + } + } + } +#endif + + // Is not an editor build, default to false here + return false; +} + +void UVRExpansionFunctionLibrary::NonAuthorityMinimumAreaRectangle(class UObject* WorldContextObject, const TArray& InVerts, const FVector& SampleSurfaceNormal, FVector& OutRectCenter, FRotator& OutRectRotation, float& OutSideLengthX, float& OutSideLengthY, bool bDebugDraw) +{ + float MinArea = -1.f; + float CurrentArea = -1.f; + FVector SupportVectorA, SupportVectorB; + FVector RectSideA, RectSideB; + float MinDotResultA, MinDotResultB, MaxDotResultA, MaxDotResultB; + FVector TestEdge; + float TestEdgeDot = 0.f; + FVector PolyNormal(0.f, 0.f, 1.f); + TArray PolyVertIndices; + + // Bail if we receive an empty InVerts array + if (InVerts.Num() == 0) + { + return; + } + + // Compute the approximate normal of the poly, using the direction of SampleSurfaceNormal for guidance + PolyNormal = (InVerts[InVerts.Num() / 3] - InVerts[0]) ^ (InVerts[InVerts.Num() * 2 / 3] - InVerts[InVerts.Num() / 3]); + if ((PolyNormal | SampleSurfaceNormal) < 0.f) + { + PolyNormal = -PolyNormal; + } + + // Transform the sample points to 2D + FMatrix SurfaceNormalMatrix = FRotationMatrix::MakeFromZX(PolyNormal, FVector(1.f, 0.f, 0.f)); + TArray TransformedVerts; + OutRectCenter = FVector(0.f); + for (int32 Idx = 0; Idx < InVerts.Num(); ++Idx) + { + OutRectCenter += InVerts[Idx]; + TransformedVerts.Add(SurfaceNormalMatrix.InverseTransformVector(InVerts[Idx])); + } + OutRectCenter /= InVerts.Num(); + + // Compute the convex hull of the sample points + ConvexHull2D::ComputeConvexHull(TransformedVerts, PolyVertIndices); + + // Minimum area rectangle as computed by http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf + for (int32 Idx = 1; Idx < PolyVertIndices.Num() - 1; ++Idx) + { + SupportVectorA = (TransformedVerts[PolyVertIndices[Idx]] - TransformedVerts[PolyVertIndices[Idx - 1]]).GetSafeNormal(); + SupportVectorA.Z = 0.f; + SupportVectorB.X = -SupportVectorA.Y; + SupportVectorB.Y = SupportVectorA.X; + SupportVectorB.Z = 0.f; + MinDotResultA = MinDotResultB = MaxDotResultA = MaxDotResultB = 0.f; + + for (int TestVertIdx = 1; TestVertIdx < PolyVertIndices.Num(); ++TestVertIdx) + { + TestEdge = TransformedVerts[PolyVertIndices[TestVertIdx]] - TransformedVerts[PolyVertIndices[0]]; + TestEdgeDot = SupportVectorA | TestEdge; + if (TestEdgeDot < MinDotResultA) + { + MinDotResultA = TestEdgeDot; + } + else if (TestEdgeDot > MaxDotResultA) + { + MaxDotResultA = TestEdgeDot; + } + + TestEdgeDot = SupportVectorB | TestEdge; + if (TestEdgeDot < MinDotResultB) + { + MinDotResultB = TestEdgeDot; + } + else if (TestEdgeDot > MaxDotResultB) + { + MaxDotResultB = TestEdgeDot; + } + } + + CurrentArea = (MaxDotResultA - MinDotResultA) * (MaxDotResultB - MinDotResultB); + if (MinArea < 0.f || CurrentArea < MinArea) + { + MinArea = CurrentArea; + RectSideA = SupportVectorA * (MaxDotResultA - MinDotResultA); + RectSideB = SupportVectorB * (MaxDotResultB - MinDotResultB); + } + } + + RectSideA = SurfaceNormalMatrix.TransformVector(RectSideA); + RectSideB = SurfaceNormalMatrix.TransformVector(RectSideB); + OutRectRotation = FRotationMatrix::MakeFromZX(PolyNormal, RectSideA).Rotator(); + OutSideLengthX = RectSideA.Size(); + OutSideLengthY = RectSideB.Size(); + +#if ENABLE_DRAW_DEBUG + if (bDebugDraw) + { + UWorld* World = (WorldContextObject) ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) : nullptr; + if (World != nullptr) + { + DrawDebugSphere(World, OutRectCenter, 10.f, 12, FColor::Yellow, true); + DrawDebugCoordinateSystem(World, OutRectCenter, SurfaceNormalMatrix.Rotator(), 100.f, true); + DrawDebugLine(World, OutRectCenter - RectSideA * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideA * 0.5f + FVector(0, 0, 10.f), FColor::Green, true, -1, 0, 5.f); + DrawDebugLine(World, OutRectCenter - RectSideB * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideB * 0.5f + FVector(0, 0, 10.f), FColor::Blue, true, -1, 0, 5.f); + } + else + { + FFrame::KismetExecutionMessage(TEXT("WorldContext required for MinimumAreaRectangle to draw a debug visualization."), ELogVerbosity::Warning); + } + } +#endif +} + +bool UVRExpansionFunctionLibrary::EqualEqual_FBPActorGripInformation(const FBPActorGripInformation& A, const FBPActorGripInformation& B) +{ + return A == B; +} + +bool UVRExpansionFunctionLibrary::IsActiveGrip(const FBPActorGripInformation& Grip) +{ + return Grip.IsActive(); +} + +FTransform_NetQuantize UVRExpansionFunctionLibrary::MakeTransform_NetQuantize(FVector Translation, FRotator Rotation, FVector Scale) +{ + return FTransform_NetQuantize(Rotation, Translation, Scale); +} + +void UVRExpansionFunctionLibrary::BreakTransform_NetQuantize(const FTransform_NetQuantize& InTransform, FVector& Translation, FRotator& Rotation, FVector& Scale) +{ + Translation = InTransform.GetLocation(); + Rotation = InTransform.Rotator(); + Scale = InTransform.GetScale3D(); +} + +FTransform_NetQuantize UVRExpansionFunctionLibrary::Conv_TransformToTransformNetQuantize(const FTransform& InTransform) +{ + return FTransform_NetQuantize(InTransform); +} + +UGripMotionControllerComponent* UVRExpansionFunctionLibrary::Conv_GripPairToMotionController(const FBPGripPair& GripPair) +{ + return GripPair.HoldingController; +} + +uint8 UVRExpansionFunctionLibrary::Conv_GripPairToGripID(const FBPGripPair& GripPair) +{ + return GripPair.GripID; +} + +FVector_NetQuantize UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize(const FVector& InVector) +{ + return FVector_NetQuantize(InVector); +} + +FVector_NetQuantize UVRExpansionFunctionLibrary::MakeVector_NetQuantize(FVector InVector) +{ + return FVector_NetQuantize(InVector); +} + +FVector_NetQuantize10 UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize10(const FVector& InVector) +{ + return FVector_NetQuantize10(InVector); +} + +FVector_NetQuantize10 UVRExpansionFunctionLibrary::MakeVector_NetQuantize10(FVector InVector) +{ + return FVector_NetQuantize10(InVector); +} + +FVector_NetQuantize100 UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize100(const FVector& InVector) +{ + return FVector_NetQuantize100(InVector); +} + +FVector_NetQuantize100 UVRExpansionFunctionLibrary::MakeVector_NetQuantize100(FVector InVector) +{ + return FVector_NetQuantize100(InVector); +} + +USceneComponent* UVRExpansionFunctionLibrary::AddSceneComponentByClass(UObject* Outer, TSubclassOf Class, const FTransform& ComponentRelativeTransform) +{ + if (Class != nullptr && Outer != nullptr) + { + USceneComponent* Component = NewObject(Outer, *Class); + if (Component != nullptr) + { + if (USceneComponent* ParentComp = Cast(Outer)) + Component->SetupAttachment(ParentComp); + + Component->RegisterComponent(); + Component->SetRelativeTransform(ComponentRelativeTransform); + + return Component; + } + else + { + return nullptr; + } + } + + return nullptr; +} + +void UVRExpansionFunctionLibrary::SmoothUpdateLaserSpline(USplineComponent* LaserSplineComponent, TArray LaserSplineMeshComponents, FVector InStartLocation, FVector InEndLocation, FVector InForward, float LaserRadius) +{ + if (LaserSplineComponent == nullptr) + return; + + LaserSplineComponent->ClearSplinePoints(); + + const FVector SmoothLaserDirection = InEndLocation - InStartLocation; + float Distance = SmoothLaserDirection.Size(); + const FVector StraightLaserEndLocation = InStartLocation + (InForward * Distance); + const int32 NumLaserSplinePoints = LaserSplineMeshComponents.Num(); + + LaserSplineComponent->AddSplinePoint(InStartLocation, ESplineCoordinateSpace::World, false); + for (int32 Index = 1; Index < NumLaserSplinePoints; Index++) + { + float Alpha = (float)Index / (float)NumLaserSplinePoints; + Alpha = FMath::Sin(Alpha * PI * 0.5f); + const FVector PointOnStraightLaser = FMath::Lerp(InStartLocation, StraightLaserEndLocation, Alpha); + const FVector PointOnSmoothLaser = FMath::Lerp(InStartLocation, InEndLocation, Alpha); + const FVector PointBetweenLasers = FMath::Lerp(PointOnStraightLaser, PointOnSmoothLaser, Alpha); + LaserSplineComponent->AddSplinePoint(PointBetweenLasers, ESplineCoordinateSpace::World, false); + } + LaserSplineComponent->AddSplinePoint(InEndLocation, ESplineCoordinateSpace::World, false); + + // Update all the segments of the spline + LaserSplineComponent->UpdateSpline(); + + const float LaserPointerRadius = LaserRadius; + Distance *= 0.0001f; + for (int32 Index = 0; Index < NumLaserSplinePoints; Index++) + { + USplineMeshComponent* SplineMeshComponent = LaserSplineMeshComponents[Index]; + check(SplineMeshComponent != nullptr); + + FVector StartLoc, StartTangent, EndLoc, EndTangent; + LaserSplineComponent->GetLocationAndTangentAtSplinePoint(Index, StartLoc, StartTangent, ESplineCoordinateSpace::Local); + LaserSplineComponent->GetLocationAndTangentAtSplinePoint(Index + 1, EndLoc, EndTangent, ESplineCoordinateSpace::Local); + + const float AlphaIndex = (float)Index / (float)NumLaserSplinePoints; + const float AlphaDistance = Distance * AlphaIndex; + float Radius = LaserPointerRadius * ((AlphaIndex * AlphaDistance) + 1); + FVector2D LaserScale(Radius, Radius); + SplineMeshComponent->SetStartScale(LaserScale, false); + + const float NextAlphaIndex = (float)(Index + 1) / (float)NumLaserSplinePoints; + const float NextAlphaDistance = Distance * NextAlphaIndex; + Radius = LaserPointerRadius * ((NextAlphaIndex * NextAlphaDistance) + 1); + LaserScale = FVector2D(Radius, Radius); + SplineMeshComponent->SetEndScale(LaserScale, false); + + SplineMeshComponent->SetStartAndEnd(StartLoc, StartTangent, EndLoc, EndTangent, true); + } + +} + +bool UVRExpansionFunctionLibrary::MatchesAnyTagsWithDirectParentTag(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, const FGameplayTagContainer& OtherContainer) +{ + TArray BaseContainerTags; + BaseContainer.GetGameplayTagArray(BaseContainerTags); + + for (const FGameplayTag& OtherTag : BaseContainerTags) + { + if (OtherTag.RequestDirectParent().MatchesTagExact(DirectParentTag)) + { + if (OtherContainer.HasTagExact(OtherTag)) + return true; + } + } + + return false; +} + +bool UVRExpansionFunctionLibrary::GetFirstGameplayTagWithExactParent(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, FGameplayTag& FoundTag) +{ + TArray BaseContainerTags; + BaseContainer.GetGameplayTagArray(BaseContainerTags); + + for (const FGameplayTag& OtherTag : BaseContainerTags) + { + if (OtherTag.RequestDirectParent().MatchesTagExact(DirectParentTag)) + { + FoundTag = OtherTag; + return true; + } + } + + return false; +} + +void UVRExpansionFunctionLibrary::ResetPeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter) +{ + TargetPeakFilter.Reset(); +} + +void UVRExpansionFunctionLibrary::UpdatePeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter, FVector NewSample) +{ + TargetPeakFilter.AddSample(NewSample); +} + +FVector UVRExpansionFunctionLibrary::GetPeak_PeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter) +{ + return TargetPeakFilter.GetPeak(); +} + +void UVRExpansionFunctionLibrary::ResetEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter) + +{ + TargetEuroFilter.ResetSmoothingFilter(); +} +void UVRExpansionFunctionLibrary::RunEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter, FVector InRawValue, const float DeltaTime, FVector & SmoothedValue) + +{ + SmoothedValue = TargetEuroFilter.RunFilterSmoothing(InRawValue, DeltaTime); +} + \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp new file mode 100644 index 0000000..707dd4e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp @@ -0,0 +1,57 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRExpansionPlugin.h" + +#include "Grippables/GrippablePhysicsReplication.h" + +#include "VRGlobalSettings.h" +#include "ISettingsContainer.h" +#include "ISettingsModule.h" +#include "ISettingsSection.h" +#include "Physics/Experimental/PhysScene_Chaos.h" + +#define LOCTEXT_NAMESPACE "FVRExpansionPluginModule" + +void FVRExpansionPluginModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + RegisterSettings(); + + FPhysScene_Chaos::PhysicsReplicationFactory = MakeShared(); +} + +void FVRExpansionPluginModule::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. + UnregisterSettings(); +} + +void FVRExpansionPluginModule::RegisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + // Create the new category + ISettingsContainerPtr SettingsContainer = SettingsModule->GetContainer("Project"); + + + SettingsModule->RegisterSettings("Project", "Plugins", "VRExpansionPlugin", + LOCTEXT("VRExpansionSettingsName", "VRExpansion Settings"), + LOCTEXT("VRExpansionSettingsDescription", "Configure global settings for the VRExpansionPlugin"), + GetMutableDefault()); + } +} + +void FVRExpansionPluginModule::UnregisterSettings() +{ + // Ensure to unregister all of your registered settings here, hot-reload would + // otherwise yield unexpected results. + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "VRExpansionPlugin"); + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FVRExpansionPluginModule, VRExpansionPlugin) \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h new file mode 100644 index 0000000..7abba6d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h @@ -0,0 +1,20 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +//#include "VRExpansionPlugin.h" +//#include "EngineMinimal.h" +//#include "VRBPDatatypes.h" +//#include "GripMotionControllerComponent.h" +//#include "VRExpansionFunctionLibrary.h" +//#include "ReplicatedVRCameraComponent.h" +//#include "ParentRelativeAttachmentComponent.h" +//#include "VRRootComponent.h" +//#include "VRBaseCharacterMovementComponent.h" +//#include "VRCharacterMovementComponent.h" +//#include "VRCharacter.h" +//#include "VRPathFollowingComponent.h" +//#include "VRPlayerController.h" +//#include "VRGripInterface.h" +//#include "Runtime/Launch/Resources/Version.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. \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp new file mode 100644 index 0000000..ca3e61d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp @@ -0,0 +1,791 @@ +#include "VRGestureComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRGestureComponent) + +#include "VRBaseCharacter.h" +#include "Components/SplineMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Components/LineBatchComponent.h" +#include "DrawDebugHelpers.h" +#include "Algo/Reverse.h" +#include "TimerManager.h" + +DECLARE_CYCLE_STAT(TEXT("TickGesture ~ TickingGesture"), STAT_TickGesture, STATGROUP_TickGesture); + +UVRGestureComponent::UVRGestureComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; + //PrimaryComponentTick.bStartWithTickEnabled = false; + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + //PrimaryComponentTick.bTickEvenWhenPaused = false; + + maxSlope = 3;// INT_MAX; + //globalThreshold = 10.0f; + SameSampleTolerance = 0.1f; + bGestureChanged = false; + MirroringHand = EVRGestureMirrorMode::GES_NoMirror; + bDrawSplinesCurved = true; + bGetGestureInWorldSpace = true; + SplineMeshScaler = FVector2D(1.f); +} + +void UGesturesDatabase::FillSplineWithGesture(FVRGesture &Gesture, USplineComponent * SplineComponent, bool bCenterPointsOnSpline, bool bScaleToBounds, float OptionalBounds, bool bUseCurvedPoints, bool bFillInSplineMeshComponents, UStaticMesh * Mesh, UMaterial * MeshMat) +{ + if (!SplineComponent || Gesture.Samples.Num() < 2) + return; + + UWorld* InWorld = GEngine->GetWorldFromContextObject(SplineComponent, EGetWorldErrorMode::LogAndReturnNull); + + if (!InWorld) + return; + + SplineComponent->ClearSplinePoints(false); + + FVector PointOffset = FVector::ZeroVector; + float Scaler = 1.0f; + if (bScaleToBounds && OptionalBounds > 0.0f) + { + Scaler = OptionalBounds / Gesture.GestureSize.GetSize().GetMax(); + } + + if (bCenterPointsOnSpline) + { + PointOffset = -Gesture.GestureSize.GetCenter(); + } + + int curIndex = 0; + for (int i = Gesture.Samples.Num() - 1; i >= 0; --i) + { + SplineComponent->AddSplinePoint((Gesture.Samples[i] + PointOffset) * Scaler, ESplineCoordinateSpace::Local, false); + curIndex++; + SplineComponent->SetSplinePointType(curIndex, bUseCurvedPoints ? ESplinePointType::Curve : ESplinePointType::Linear, false); + } + + // Update spline now + SplineComponent->UpdateSpline(); + + if (bFillInSplineMeshComponents && Mesh != nullptr && MeshMat != nullptr) + { + TArray CurrentSplineChildren; + + TArray Children; + SplineComponent->GetChildrenComponents(false, Children); + for (auto Child : Children) + { + USplineMeshComponent* SplineMesh = Cast(Child); + if (SplineMesh != nullptr && IsValid(SplineMesh)) + { + CurrentSplineChildren.Add(SplineMesh); + } + } + + if (CurrentSplineChildren.Num() > SplineComponent->GetNumberOfSplinePoints() - 1) + { + int diff = CurrentSplineChildren.Num() - (CurrentSplineChildren.Num() - (SplineComponent->GetNumberOfSplinePoints() -1)); + + for (int i = CurrentSplineChildren.Num()- 1; i >= diff; --i) + { + if (!CurrentSplineChildren[i]->IsBeingDestroyed()) + { + CurrentSplineChildren[i]->SetVisibility(false); + CurrentSplineChildren[i]->Modify(); + CurrentSplineChildren[i]->DestroyComponent(); + CurrentSplineChildren.RemoveAt(i); + } + } + } + else + { + for (int i = CurrentSplineChildren.Num(); i < SplineComponent->GetNumberOfSplinePoints() -1; ++i) + { + USplineMeshComponent * newSplineMesh = NewObject(SplineComponent); + + newSplineMesh->RegisterComponentWithWorld(InWorld); + newSplineMesh->SetMobility(EComponentMobility::Movable); + CurrentSplineChildren.Add(newSplineMesh); + newSplineMesh->SetStaticMesh(Mesh); + newSplineMesh->SetMaterial(0, (UMaterialInterface*)MeshMat); + + newSplineMesh->AttachToComponent(SplineComponent, FAttachmentTransformRules::SnapToTargetIncludingScale); + newSplineMesh->SetVisibility(true); + } + } + + + for(int i=0; iGetNumberOfSplinePoints() - 1; i++) + { + CurrentSplineChildren[i]->SetStartAndEnd(SplineComponent->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::Local), + SplineComponent->GetTangentAtSplinePoint(i, ESplineCoordinateSpace::Local), + SplineComponent->GetLocationAtSplinePoint(i + 1, ESplineCoordinateSpace::Local), + SplineComponent->GetTangentAtSplinePoint(i + 1, ESplineCoordinateSpace::Local), + true); + } + } + +} + +void UVRGestureComponent::BeginRecording(bool bRunDetection, bool bFlattenGesture, bool bDrawGesture, bool bDrawAsSpline, int SamplingHTZ, int SampleBufferSize, float ClampingTolerance) +{ + RecordingBufferSize = SampleBufferSize; + RecordingDelta = 1.0f / SamplingHTZ; + RecordingClampingTolerance = ClampingTolerance; + bDrawRecordingGesture = bDrawGesture; + bDrawRecordingGestureAsSpline = bDrawAsSpline; + bRecordingFlattenGesture = bFlattenGesture; + GestureLog.GestureSize.Init(); + + // Reset does the reserve already + GestureLog.Samples.Reset(RecordingBufferSize); + + CurrentState = bRunDetection ? EVRGestureState::GES_Detecting : EVRGestureState::GES_Recording; + + if (TargetCharacter != nullptr) + { + OriginatingTransform = TargetCharacter->OffsetComponentToWorld; + if (!bGetGestureInWorldSpace) + { + ParentRelativeTransform = OriginatingTransform.GetRelativeTransform(TargetCharacter->GetActorTransform()); + } + else + { + ParentRelativeTransform = FTransform::Identity; + } + } + else if (AVRBaseCharacter * own = Cast(GetOwner())) + { + TargetCharacter = own; + OriginatingTransform = TargetCharacter->OffsetComponentToWorld; + if (!bGetGestureInWorldSpace) + { + ParentRelativeTransform = OriginatingTransform.GetRelativeTransform(TargetCharacter->GetActorTransform()); + } + else + { + ParentRelativeTransform = FTransform::Identity; + } + } + else + OriginatingTransform = this->GetComponentTransform(); + + StartVector = OriginatingTransform.InverseTransformPosition(this->GetComponentLocation()); + + // Reinit the drawing spline + if (!bDrawAsSpline || !bDrawGesture) + RecordingGestureDraw.Clear(); // Not drawing or not as a spline, remove the components if they exist + else + { + RecordingGestureDraw.Reset(); // Otherwise just clear points and hide mesh components + + if (RecordingGestureDraw.SplineComponent == nullptr) + { + RecordingGestureDraw.SplineComponent = NewObject(GetAttachParent()); + RecordingGestureDraw.SplineComponent->RegisterComponentWithWorld(GetWorld()); + RecordingGestureDraw.SplineComponent->SetMobility(EComponentMobility::Movable); + } + + RecordingGestureDraw.SplineComponent->ClearSplinePoints(true); + if (!bGetGestureInWorldSpace && TargetCharacter != nullptr) + { + RecordingGestureDraw.SplineComponent->AttachToComponent(TargetCharacter->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + RecordingGestureDraw.SplineComponent->SetRelativeLocationAndRotation(ParentRelativeTransform.TransformPosition(StartVector), ParentRelativeTransform.GetRotation()); + } + else + { + RecordingGestureDraw.SplineComponent->AttachToComponent(GetAttachParent(), FAttachmentTransformRules::KeepRelativeTransform); + } + } + + this->SetComponentTickEnabled(true); + + if (!TickGestureTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(TickGestureTimer_Handle, this, &UVRGestureComponent::TickGesture, RecordingDelta, true); +} + +void UVRGestureComponent::CaptureGestureFrame() +{ + FTransform CalcedTransform = OriginatingTransform; + if (!bGetGestureInWorldSpace) + { + if (TargetCharacter != nullptr) + { + CalcedTransform = ParentRelativeTransform * TargetCharacter->GetActorTransform(); + } + else if (AVRBaseCharacter* own = Cast(GetOwner())) + { + TargetCharacter = own; + CalcedTransform = ParentRelativeTransform * TargetCharacter->GetActorTransform(); + } + } + + FVector NewSample = CalcedTransform.InverseTransformPosition(this->GetComponentLocation()) - StartVector; + + if (CurrentState == EVRGestureState::GES_Recording) + { + if (bRecordingFlattenGesture) + NewSample.X = 0; + + if (RecordingClampingTolerance > 0.0f) + { + NewSample.X = FMath::GridSnap(NewSample.X, RecordingClampingTolerance); + NewSample.Y = FMath::GridSnap(NewSample.Y, RecordingClampingTolerance); + NewSample.Z = FMath::GridSnap(NewSample.Z, RecordingClampingTolerance); + } + } + + // Add in newest sample at beginning (reverse order) + if (NewSample != FVector::ZeroVector && (GestureLog.Samples.Num() < 1 || !GestureLog.Samples[0].Equals(NewSample, SameSampleTolerance))) + { + bool bClearLatestSpline = false; + // Pop off oldest sample + if (GestureLog.Samples.Num() >= RecordingBufferSize) + { + GestureLog.Samples.Pop(false); + bClearLatestSpline = true; + } + + GestureLog.GestureSize.Max.X = FMath::Max(NewSample.X, GestureLog.GestureSize.Max.X); + GestureLog.GestureSize.Max.Y = FMath::Max(NewSample.Y, GestureLog.GestureSize.Max.Y); + GestureLog.GestureSize.Max.Z = FMath::Max(NewSample.Z, GestureLog.GestureSize.Max.Z); + + GestureLog.GestureSize.Min.X = FMath::Min(NewSample.X, GestureLog.GestureSize.Min.X); + GestureLog.GestureSize.Min.Y = FMath::Min(NewSample.Y, GestureLog.GestureSize.Min.Y); + GestureLog.GestureSize.Min.Z = FMath::Min(NewSample.Z, GestureLog.GestureSize.Min.Z); + + + if (bDrawRecordingGesture && bDrawRecordingGestureAsSpline && SplineMesh != nullptr && SplineMaterial != nullptr) + { + if (bClearLatestSpline) + RecordingGestureDraw.ClearLastPoint(); + + RecordingGestureDraw.SplineComponent->AddSplinePoint(NewSample, ESplineCoordinateSpace::Local, false); + int SplineIndex = RecordingGestureDraw.SplineComponent->GetNumberOfSplinePoints() - 1; + RecordingGestureDraw.SplineComponent->SetSplinePointType(SplineIndex, bDrawSplinesCurved ? ESplinePointType::Curve : ESplinePointType::Linear, true); + + bool bFoundEmptyMesh = false; + USplineMeshComponent * MeshComp = nullptr; + int MeshIndex = 0; + + for (int i = 0; i < RecordingGestureDraw.SplineMeshes.Num(); i++) + { + MeshIndex = i; + MeshComp = RecordingGestureDraw.SplineMeshes[i]; + if (MeshComp == nullptr) + { + RecordingGestureDraw.SplineMeshes[i] = NewObject(RecordingGestureDraw.SplineComponent); + MeshComp = RecordingGestureDraw.SplineMeshes[i]; + + MeshComp->RegisterComponentWithWorld(GetWorld()); + MeshComp->SetMobility(EComponentMobility::Movable); + //MeshComp->SetStaticMesh(SplineMesh); + //MeshComp->SetMaterial(0, (UMaterialInterface*)SplineMaterial); + bFoundEmptyMesh = true; + break; + } + else if (!MeshComp->IsVisible()) + { + bFoundEmptyMesh = true; + break; + } + } + + if (!bFoundEmptyMesh) + { + USplineMeshComponent * newSplineMesh = NewObject(RecordingGestureDraw.SplineComponent); + MeshComp = newSplineMesh; + MeshComp->RegisterComponentWithWorld(GetWorld()); + MeshComp->SetMobility(EComponentMobility::Movable); + RecordingGestureDraw.SplineMeshes.Add(MeshComp); + MeshIndex = RecordingGestureDraw.SplineMeshes.Num() - 1; + //MeshComp->SetStaticMesh(SplineMesh); + //MeshComp->SetMaterial(0, (UMaterialInterface*)SplineMaterial); + if (!bGetGestureInWorldSpace && TargetCharacter) + MeshComp->AttachToComponent(TargetCharacter->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + } + + if (MeshComp != nullptr) + { + // Fill in last mesh component tangent and end pos + if (RecordingGestureDraw.LastIndexSet != MeshIndex && RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet] != nullptr) + { + RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet]->SetEndPosition(NewSample, false); + RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet]->SetEndTangent(RecordingGestureDraw.SplineComponent->GetTangentAtSplinePoint(SplineIndex, ESplineCoordinateSpace::Local), true); + } + + // Re-init mesh and material on the spline mesh, won't do anything if its the same + MeshComp->SetStaticMesh(SplineMesh); + MeshComp->SetMaterial(0, (UMaterialInterface*)SplineMaterial); + + MeshComp->SetStartScale(SplineMeshScaler); + MeshComp->SetEndScale(SplineMeshScaler); + MeshComp->SetStartAndEnd(NewSample, + RecordingGestureDraw.SplineComponent->GetTangentAtSplinePoint(SplineIndex, ESplineCoordinateSpace::Local), + NewSample, + FVector::ZeroVector, + true); + + //if (bGetGestureInWorldSpace) + MeshComp->SetWorldLocationAndRotation(CalcedTransform.TransformPosition(StartVector), CalcedTransform.GetRotation()); + //else + //MeshComp->SetRelativeLocationAndRotation(/*OriginatingTransform.TransformPosition(*/StartVector/*)*/, FQuat::Identity/*OriginatingTransform.GetRotation()*/); + + RecordingGestureDraw.LastIndexSet = MeshIndex; + MeshComp->SetVisibility(true); + } + + } + + GestureLog.Samples.Insert(NewSample, 0); + bGestureChanged = true; + } +} + +void UVRGestureComponent::TickGesture() +{ + SCOPE_CYCLE_COUNTER(STAT_TickGesture); + + switch (CurrentState) + { + case EVRGestureState::GES_Detecting: + { + CaptureGestureFrame(); + RecognizeGesture(GestureLog); + bGestureChanged = false; + }break; + + case EVRGestureState::GES_Recording: + { + CaptureGestureFrame(); + }break; + + case EVRGestureState::GES_None: + default: {}break; + } + + if (bDrawRecordingGesture) + { + if (!bDrawRecordingGestureAsSpline) + { + FTransform DrawTransform = FTransform(StartVector) * OriginatingTransform; + // Setting the lifetime to the recording htz now, should remove the flicker. + DrawDebugGesture(this, DrawTransform, GestureLog, FColor::White, false, 0, RecordingDelta, 0.0f); + } + } +} + +void UVRGestureComponent::RecognizeGesture(FVRGesture inputGesture) +{ + if (!GesturesDB || inputGesture.Samples.Num() < 1 || !bGestureChanged) + return; + + float minDist = MAX_FLT; + + int OutGestureIndex = -1; + bool bMirrorGesture = false; + + FVector Size = inputGesture.GestureSize.GetSize(); + float Scaler = GesturesDB->TargetGestureScale / Size.GetMax(); + float FinalScaler = Scaler; + + for (int i = 0; i < GesturesDB->Gestures.Num(); i++) + { + FVRGesture &exampleGesture = GesturesDB->Gestures[i]; + + if (!exampleGesture.GestureSettings.bEnabled || exampleGesture.Samples.Num() < 1 || inputGesture.Samples.Num() < exampleGesture.GestureSettings.Minimum_Gesture_Length) + continue; + + FinalScaler = exampleGesture.GestureSettings.bEnableScaling ? Scaler : 1.f; + + bMirrorGesture = (MirroringHand != EVRGestureMirrorMode::GES_NoMirror && MirroringHand != EVRGestureMirrorMode::GES_MirrorBoth && MirroringHand == exampleGesture.GestureSettings.MirrorMode); + + if (GetGestureDistance(inputGesture.Samples[0] * FinalScaler, exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture, FinalScaler) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + else if (exampleGesture.GestureSettings.MirrorMode == EVRGestureMirrorMode::GES_MirrorBoth) + { + bMirrorGesture = true; + if (GetGestureDistance(inputGesture.Samples[0] * FinalScaler, exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture, FinalScaler) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + } + + /*if (exampleGesture.MirrorMode == EVRGestureMirrorMode::GES_MirrorBoth) + { + bMirrorGesture = true; + + if (GetGestureDistance(inputGesture.Samples[0], exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + }*/ + } + + if (/*minDist < FMath::Square(globalThreshold) && */OutGestureIndex != -1) + { + OnGestureDetected(GesturesDB->Gestures[OutGestureIndex].GestureType, /*minDist,*/ GesturesDB->Gestures[OutGestureIndex].Name, OutGestureIndex, GesturesDB, Size); + OnGestureDetected_Bind.Broadcast(GesturesDB->Gestures[OutGestureIndex].GestureType, /*minDist,*/ GesturesDB->Gestures[OutGestureIndex].Name, OutGestureIndex, GesturesDB, Size); + ClearRecording(); // Clear the recording out, we don't want to detect this gesture again with the same data + RecordingGestureDraw.Reset(); + } +} + +float UVRGestureComponent::dtw(FVRGesture seq1, FVRGesture seq2, bool bMirrorGesture, float Scaler) +{ + + // #TODO: Skip copying the array and reversing it in the future, we only ever use the reversed value. + // So pre-reverse it and keep it stored like that on init. When we do the initial sample we can check off of the first index instead of last then + + // Should also be able to get SizeSquared for values and compared to squared thresholds instead of doing the full SQRT calc. + + // Getting number of average samples recorded over of a gesture (top down) may be able to achieve a basic % completed check + // to see how far into detecting a gesture we are, this would require ignoring the last position threshold though.... + + int RowCount = seq1.Samples.Num() + 1; + int ColumnCount = seq2.Samples.Num() + 1; + + TArray LookupTable; + LookupTable.AddZeroed(ColumnCount * RowCount); + + TArray SlopeI; + SlopeI.AddZeroed(ColumnCount * RowCount); + TArray SlopeJ; + SlopeJ.AddZeroed(ColumnCount * RowCount); + + for (int i = 1; i < (ColumnCount * RowCount); i++) + { + LookupTable[i] = MAX_FLT; + } + // Don't need to do this, it is already handled by add zeroed + //tab[0, 0] = 0; + + int icol = 0, icolneg = 0; + + // Dynamic computation of the DTW matrix. + for (int i = 1; i < RowCount; i++) + { + for (int j = 1; j < ColumnCount; j++) + { + icol = i * ColumnCount; + icolneg = icol - ColumnCount;// (i - 1) * ColumnCount; + + if ( + LookupTable[icol + (j - 1)] < LookupTable[icolneg + (j - 1)] && + LookupTable[icol + (j - 1)] < LookupTable[icolneg + j] && + SlopeI[icol + (j - 1)] < maxSlope) + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icol + j - 1]; + SlopeI[icol + j] = SlopeJ[icol + j - 1] + 1; + SlopeJ[icol + j] = 0; + } + else if ( + LookupTable[icolneg + j] < LookupTable[icolneg + j - 1] && + LookupTable[icolneg + j] < LookupTable[icol + j - 1] && + SlopeJ[icolneg + j] < maxSlope) + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icolneg + j]; + SlopeI[icol + j] = 0; + SlopeJ[icol + j] = SlopeJ[icolneg + j] + 1; + } + else + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icolneg + j - 1]; + SlopeI[icol + j] = 0; + SlopeJ[icol + j] = 0; + } + } + } + + // Find best between seq2 and an ending (postfix) of seq1. + float bestMatch = FLT_MAX; + + for (int i = 1; i < seq1.Samples.Num() + 1/* - seq2.Minimum_Gesture_Length*/; i++) + { + if (LookupTable[(i*ColumnCount) + seq2.Samples.Num()] < bestMatch) + bestMatch = LookupTable[(i*ColumnCount) + seq2.Samples.Num()]; + } + + return bestMatch; +} + +void UVRGestureComponent::DrawDebugGesture(UObject* WorldContextObject, FTransform &StartTransform, FVRGesture GestureToDraw, FColor const& Color, bool bPersistentLines, uint8 DepthPriority, float LifeTime, float Thickness) +{ +#if ENABLE_DRAW_DEBUG + + UWorld* InWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + + if (InWorld != nullptr) + { + // no debug line drawing on dedicated server + if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer && GestureToDraw.Samples.Num() > 1) + { + bool bMirrorGesture = (MirroringHand != EVRGestureMirrorMode::GES_NoMirror && MirroringHand == GestureToDraw.GestureSettings.MirrorMode); + FVector MirrorVector = FVector(1.f, -1.f, 1.f); // Only mirroring on Y axis to flip Left/Right + + // this means foreground lines can't be persistent + ULineBatchComponent* const LineBatcher = (InWorld ? ((DepthPriority == SDPG_Foreground) ? InWorld->ForegroundLineBatcher : ((bPersistentLines || (LifeTime > 0.f)) ? InWorld->PersistentLineBatcher : InWorld->LineBatcher)) : NULL); + + if (LineBatcher != NULL) + { + float const LineLifeTime = (LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime; + + TArray Lines; + FBatchedLine Line; + Line.Color = Color; + Line.Thickness = Thickness; + Line.RemainingLifeTime = LineLifeTime; + Line.DepthPriority = DepthPriority; + + FVector FirstLoc = bMirrorGesture ? GestureToDraw.Samples[GestureToDraw.Samples.Num() - 1] * MirrorVector : GestureToDraw.Samples[GestureToDraw.Samples.Num() - 1]; + + for (int i = GestureToDraw.Samples.Num() - 2; i >= 0; --i) + { + Line.Start = bMirrorGesture ? GestureToDraw.Samples[i] * MirrorVector : GestureToDraw.Samples[i]; + + Line.End = FirstLoc; + FirstLoc = Line.Start; + + Line.End = StartTransform.TransformPosition(Line.End); + Line.Start = StartTransform.TransformPosition(Line.Start); + + Lines.Add(Line); + } + + LineBatcher->DrawLines(Lines); + } + } + } +#endif +} + +void UGesturesDatabase::RecalculateGestures(bool bScaleToDatabase) +{ + for (int i = 0; i < Gestures.Num(); ++i) + { + Gestures[i].CalculateSizeOfGesture(bScaleToDatabase, TargetGestureScale); + } +} + +bool UGesturesDatabase::ImportSplineAsGesture(USplineComponent * HostSplineComponent, FString GestureName, bool bKeepSplineCurves, float SegmentLen, bool bScaleToDatabase) +{ + FVRGesture NewGesture; + + if (HostSplineComponent->GetNumberOfSplinePoints() < 2) + return false; + + NewGesture.Name = GestureName; + + FVector FirstPointPos = HostSplineComponent->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::Local); + + float LastDistance = 0.f; + float ThisDistance = 0.f; + FVector LastDistanceV; + FVector ThisDistanceV; + FVector DistNormal; + float DistAlongSegment = 0.f; + + // Realign to xForward on the gesture, normally splines lay out as X to the right + FTransform Realignment = FTransform(FRotator(0.f, 90.f, 0.f), -FirstPointPos); + + // Prefill the first point + NewGesture.Samples.Add(Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(HostSplineComponent->GetNumberOfSplinePoints() - 1, ESplineCoordinateSpace::Local))); + + // Inserting in reverse order -2 so we start one down + for (int i = HostSplineComponent->GetNumberOfSplinePoints() - 2; i >= 0; --i) + { + if (bKeepSplineCurves) + { + LastDistance = HostSplineComponent->GetDistanceAlongSplineAtSplinePoint(i + 1); + ThisDistance = HostSplineComponent->GetDistanceAlongSplineAtSplinePoint(i); + + DistAlongSegment = FMath::Abs(ThisDistance - LastDistance); + } + else + { + LastDistanceV = Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(i + 1, ESplineCoordinateSpace::Local)); + ThisDistanceV = Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::Local)); + + DistAlongSegment = FVector::Dist(ThisDistanceV, LastDistanceV); + DistNormal = ThisDistanceV - LastDistanceV; + DistNormal.Normalize(); + } + + + float SegmentCount = FMath::FloorToFloat(DistAlongSegment / SegmentLen); + float OverFlow = FMath::Fmod(DistAlongSegment, SegmentLen); + + if (SegmentCount < 1) + { + SegmentCount++; + } + + float DistPerSegment = (DistAlongSegment / SegmentCount); + + for (int j = 0; j < SegmentCount; j++) + { + if (j == SegmentCount - 1 && i > 0) + DistPerSegment += OverFlow; + + if (bKeepSplineCurves) + { + LastDistance -= DistPerSegment; + if (j == SegmentCount - 1 && i > 0) + { + LastDistance = ThisDistance; + } + FVector loc = Realignment.TransformPosition(HostSplineComponent->GetLocationAtDistanceAlongSpline(LastDistance, ESplineCoordinateSpace::Local)); + + if (!loc.IsNearlyZero()) + NewGesture.Samples.Add(loc); + } + else + { + LastDistanceV += DistPerSegment * DistNormal; + + if (j == SegmentCount - 1 && i > 0) + { + LastDistanceV = ThisDistanceV; + } + + if (!LastDistanceV.IsNearlyZero()) + NewGesture.Samples.Add(LastDistanceV); + } + } + } + + NewGesture.CalculateSizeOfGesture(bScaleToDatabase, this->TargetGestureScale); + Gestures.Add(NewGesture); + return true; +} + +void FVRGestureSplineDraw::ClearLastPoint() +{ + SplineComponent->RemoveSplinePoint(0, false); + + if (SplineMeshes.Num() < NextIndexCleared + 1) + NextIndexCleared = 0; + + SplineMeshes[NextIndexCleared]->SetVisibility(false); + NextIndexCleared++; +} + +void FVRGestureSplineDraw::Reset() +{ + if (SplineComponent != nullptr) + SplineComponent->ClearSplinePoints(true); + + for (int i = SplineMeshes.Num() - 1; i >= 0; --i) + { + if (SplineMeshes[i] != nullptr) + SplineMeshes[i]->SetVisibility(false); + else + SplineMeshes.RemoveAt(i); + } + + LastIndexSet = 0; + NextIndexCleared = 0; +} + +void FVRGestureSplineDraw::Clear() +{ + for (int i = 0; i < SplineMeshes.Num(); ++i) + { + if (SplineMeshes[i] != nullptr && !SplineMeshes[i]->IsBeingDestroyed()) + { + SplineMeshes[i]->Modify(); + SplineMeshes[i]->DestroyComponent(); + } + } + SplineMeshes.Empty(); + + if (SplineComponent != nullptr) + { + SplineComponent->DestroyComponent(); + SplineComponent = nullptr; + } + + LastIndexSet = 0; + NextIndexCleared = 0; +} + +FVRGestureSplineDraw::FVRGestureSplineDraw() +{ + SplineComponent = nullptr; + NextIndexCleared = 0; + LastIndexSet = 0; +} + +FVRGestureSplineDraw::~FVRGestureSplineDraw() +{ + Clear(); +} + +void UVRGestureComponent::BeginDestroy() +{ + Super::BeginDestroy(); + RecordingGestureDraw.Clear(); + if (TickGestureTimer_Handle.IsValid()) + { + if (UWorld* MyWorld = GetWorld()) + { + MyWorld->GetTimerManager().ClearTimer(TickGestureTimer_Handle); + } + } +} + +void UVRGestureComponent::RecalculateGestureSize(FVRGesture & InputGesture, UGesturesDatabase * GestureDB) +{ + if (GestureDB != nullptr) + InputGesture.CalculateSizeOfGesture(true, GestureDB->TargetGestureScale); + else + InputGesture.CalculateSizeOfGesture(false); +} + +FVRGesture UVRGestureComponent::EndRecording() +{ + if (TickGestureTimer_Handle.IsValid()) + { + if (UWorld* MyWorld = GetWorld()) + { + MyWorld->GetTimerManager().ClearTimer(TickGestureTimer_Handle); + } + } + + this->SetComponentTickEnabled(false); + CurrentState = EVRGestureState::GES_None; + + // Reset the recording gesture + RecordingGestureDraw.Reset(); + + return GestureLog; +} + +void UVRGestureComponent::ClearRecording() +{ + GestureLog.Samples.Reset(RecordingBufferSize); +} + +void UVRGestureComponent::SaveRecording(FVRGesture &Recording, FString RecordingName, bool bScaleRecordingToDatabase) +{ + if (GesturesDB) + { + Recording.CalculateSizeOfGesture(bScaleRecordingToDatabase, GesturesDB->TargetGestureScale); + Recording.Name = RecordingName; + GesturesDB->Gestures.Add(Recording); + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp new file mode 100644 index 0000000..f201060 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp @@ -0,0 +1,466 @@ + +#include "VRGlobalSettings.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRGlobalSettings) + +#include "Chaos/ChaosConstraintSettings.h" +#include "Grippables/GrippableSkeletalMeshComponent.h" + +UVRGlobalSettings::UVRGlobalSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer), + bLerpHybridWithSweepGrips(true), + bOnlyLerpHybridRotation(false), + bHybridWithSweepUseDistanceBasedLerp(true), + HybridWithSweepLerpDuration(0.2f), + bUseGlobalLerpToHand(false), + bSkipLerpToHandIfHeld(false), + MinDistanceForLerp(10.0f), + LerpDuration(0.25f), + MinSpeedForLerp(100.f), + MaxSpeedForLerp(500.f), + LerpInterpolationMode(EVRLerpInterpolationMode::QuatInterp), + bUseCurve(false), + OneEuroMinCutoff(0.1f), + OneEuroCutoffSlope(10.0f), + OneEuroDeltaCutoff(10.0f), + CurrentControllerProfileInUse(NAME_None), + CurrentControllerProfileTransform(FTransform::Identity), + bUseSeperateHandTransforms(false), + CurrentControllerProfileTransformRight(FTransform::Identity) +{ + DefaultGrippableCharacterMeshComponentClass = UGrippableSkeletalMeshComponent::StaticClass(); + + bUseCollisionModificationForCollisionIgnore = false; + CollisionIgnoreSubsystemUpdateRate = 1.f; + + bUseChaosTranslationScalers = false; + bSetEngineChaosScalers = false; + LinearDriveStiffnessScale = 1.0f;// Chaos::ConstraintSettings::LinearDriveStiffnessScale(); + LinearDriveDampingScale = 1.0f;// Chaos::ConstraintSettings::LinearDriveDampingScale(); + AngularDriveStiffnessScale = 0.3f; // 1.5f// Chaos::ConstraintSettings::AngularDriveStiffnessScale(); + AngularDriveDampingScale = 0.3f; // 1.5f// Chaos::ConstraintSettings::AngularDriveDampingScale(); + + // Constraint settings + JointStiffness = 1.0f;// Chaos::ConstraintSettings::JointStiffness(); + SoftLinearStiffnessScale = 1.5f;// Chaos::ConstraintSettings::SoftLinearStiffnessScale(); + SoftLinearDampingScale = 1.2f;// Chaos::ConstraintSettings::SoftLinearDampingScale(); + SoftAngularStiffnessScale = 100000.f;// Chaos::ConstraintSettings::SoftAngularStiffnessScale(); + SoftAngularDampingScale = 1000.f;// Chaos::ConstraintSettings::SoftAngularDampingScale(); + JointLinearBreakScale = 1.0f; //Chaos::ConstraintSettings::LinearBreakScale(); + JointAngularBreakScale = 1.0f; //Chaos::ConstraintSettings::AngularBreakScale(); + +} + +TSubclassOf UVRGlobalSettings::GetDefaultGrippableCharacterMeshComponentClass() +{ + const UVRGlobalSettings* VRSettings = GetDefault(); + + if (VRSettings) + { + // Using a getter to stay safe from bricking peoples projects if they set it to none somehow + if (VRSettings->DefaultGrippableCharacterMeshComponentClass != nullptr) + { + return VRSettings->DefaultGrippableCharacterMeshComponentClass; + } + } + + return UGrippableSkeletalMeshComponent::StaticClass(); +} + +bool UVRGlobalSettings::IsGlobalLerpEnabled() +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + return VRSettings.bUseGlobalLerpToHand; +} + +FTransform UVRGlobalSettings::AdjustTransformByControllerProfile(FName OptionalControllerProfileName, const FTransform& SocketTransform, bool bIsRightHand) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + if (OptionalControllerProfileName == NAME_None) + { + if (VRSettings.CurrentControllerProfileInUse != NAME_None) + { + // Use currently loaded transform + return SocketTransform * (((bIsRightHand && VRSettings.bUseSeperateHandTransforms) ? VRSettings.CurrentControllerProfileTransformRight : VRSettings.CurrentControllerProfileTransform)); + } + + // No override and no default, return base transform back + return SocketTransform; + } + + // Had an override, find it if possible and use its transform + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([OptionalControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == OptionalControllerProfileName; + }); + + if (FoundProfile) + { + return SocketTransform * (((bIsRightHand && VRSettings.bUseSeperateHandTransforms) ? FoundProfile->SocketOffsetTransformRightHand : FoundProfile->SocketOffsetTransform)); + } + + // Couldn't find it, return base transform + return SocketTransform; +} + + +FTransform UVRGlobalSettings::AdjustTransformByGivenControllerProfile(UPARAM(ref) FBPVRControllerProfile& ControllerProfile, const FTransform& SocketTransform, bool bIsRightHand) +{ + // Use currently loaded transform + return SocketTransform * (((bIsRightHand && ControllerProfile.bUseSeperateHandOffsetTransforms) ? ControllerProfile.SocketOffsetTransformRightHand : ControllerProfile.SocketOffsetTransform)); + + // Couldn't find it, return base transform + return SocketTransform; +} + +TArray UVRGlobalSettings::GetControllerProfiles() +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + return VRSettings.ControllerProfiles; +} + +void UVRGlobalSettings::OverwriteControllerProfile(UPARAM(ref)FBPVRControllerProfile& OverwritingProfile, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault(); + + for (int i = 0; i < VRSettings.ControllerProfiles.Num(); ++i) + { + if (VRSettings.ControllerProfiles[i].ControllerName == OverwritingProfile.ControllerName) + { + VRSettings.ControllerProfiles[i] = OverwritingProfile; + } + } + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::AddControllerProfile(UPARAM(ref)FBPVRControllerProfile& NewProfile, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault(); + + VRSettings.ControllerProfiles.Add(NewProfile); + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::DeleteControllerProfile(FName ControllerProfileName, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault(); + + for (int i = VRSettings.ControllerProfiles.Num() - 1; i >= 0; --i) + { + if (VRSettings.ControllerProfiles[i].ControllerName == ControllerProfileName) + { + VRSettings.ControllerProfiles.RemoveAt(i); + } + } + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::SaveControllerProfiles() +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault(); + VRSettings.SaveConfig(); + + //VRSettings.SaveConfig(CPF_Config, *VRSettings.GetGlobalUserConfigFilename());//VRSettings.GetDefaultConfigFilename()); +} + + +FName UVRGlobalSettings::GetCurrentProfileName(bool& bHadLoadedProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + bHadLoadedProfile = VRSettings.CurrentControllerProfileInUse != NAME_None; + return VRSettings.CurrentControllerProfileInUse; +} + +FBPVRControllerProfile UVRGlobalSettings::GetCurrentProfile(bool& bHadLoadedProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + FName ControllerProfileName = VRSettings.CurrentControllerProfileInUse; + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + bHadLoadedProfile = FoundProfile != nullptr; + + if (bHadLoadedProfile) + { + return *FoundProfile; + } + else + return FBPVRControllerProfile(); +} + +bool UVRGlobalSettings::GetControllerProfile(FName ControllerProfileName, FBPVRControllerProfile& OutProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + if (FoundProfile) + { + OutProfile = *FoundProfile; + return true; + } + + return false; +} + +bool UVRGlobalSettings::LoadControllerProfileByName(FName ControllerProfileName, bool bSetAsCurrentProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + if (FoundProfile) + { + return LoadControllerProfile(*FoundProfile, bSetAsCurrentProfile); + } + + + UE_LOG(LogTemp, Warning, TEXT("Could not find controller profile!: %s"), *ControllerProfileName.ToString()); + return false; +} + +bool UVRGlobalSettings::LoadControllerProfile(const FBPVRControllerProfile& ControllerProfile, bool bSetAsCurrentProfile) +{ + + /* + UInputSettings* InputSettings = GetMutableDefault(); + if (false)//InputSettings != nullptr) + { + if (ControllerProfile.ActionOverrides.Num() > 0) + { + // Load button mappings + for (auto& Elem : ControllerProfile.ActionOverrides) + { + FName ActionName = Elem.Key; + FActionMappingDetails Mapping = Elem.Value; + + // We allow for 0 mapped actions here in case you want to delete one + if (ActionName == NAME_None) + continue; + + const TArray& ActionMappings = InputSettings->GetActionMappings(); + for (int32 ActionIndex = ActionMappings.Num() - 1; ActionIndex >= 0; --ActionIndex) + { + if (ActionMappings[ActionIndex].ActionName == ActionName) + { + InputSettings->RemoveActionMapping(ActionMappings[ActionIndex], false); + // we don't break because the mapping may have been in the array twice + } + } + + // Then add the new bindings + for (FInputActionKeyMapping &KeyMapping : Mapping.ActionMappings) + { + // By default the key mappings don't have an action name, add them here + KeyMapping.ActionName = ActionName; + InputSettings->AddActionMapping(KeyMapping, false); + } + } + } + + if (ControllerProfile.AxisOverrides.Num() > 0) + { + // Load axis mappings + for (auto& Elem : ControllerProfile.AxisOverrides) + { + FName AxisName = Elem.Key; + FAxisMappingDetails Mapping = Elem.Value; + + // We allow for 0 mapped Axis's here in case you want to delete one + if (AxisName == NAME_None) + continue; + + const TArray& AxisMappings = InputSettings->GetAxisMappings(); + + // Clear all Axis's that use our Axis name first + for (int32 AxisIndex = AxisMappings.Num() - 1; AxisIndex >= 0; --AxisIndex) + { + if (AxisMappings[AxisIndex].AxisName == AxisName) + { + InputSettings->RemoveAxisMapping(AxisMappings[AxisIndex], false); + // we don't break because the mapping may have been in the array twice + } + } + + // Then add the new bindings + for (FInputAxisKeyMapping &KeyMapping : Mapping.AxisMappings) + { + // By default the key mappings don't have an Axis name, add them here + KeyMapping.AxisName = AxisName; + InputSettings->AddAxisMapping(KeyMapping, false); + } + } + } + + if (ControllerProfile.ActionOverrides.Num() > 0 || ControllerProfile.AxisOverrides.Num() > 0) + { + // Tell all players to use the new keymappings + InputSettings->ForceRebuildKeymaps(); + } + }*/ + + if (bSetAsCurrentProfile) + { + UVRGlobalSettings* VRSettings = GetMutableDefault(); + if (VRSettings) + { + VRSettings->CurrentControllerProfileInUse = ControllerProfile.ControllerName; + VRSettings->CurrentControllerProfileTransform = ControllerProfile.SocketOffsetTransform; + ensure(!VRSettings->CurrentControllerProfileTransform.ContainsNaN()); + VRSettings->bUseSeperateHandTransforms = ControllerProfile.bUseSeperateHandOffsetTransforms; + VRSettings->CurrentControllerProfileTransformRight = ControllerProfile.SocketOffsetTransformRightHand; + ensure(!VRSettings->CurrentControllerProfileTransformRight.ContainsNaN()); + VRSettings->OnControllerProfileChangedEvent.Broadcast(); + } + else + return false; + } + + + // Not saving key mapping in purpose, app will revert to default on next load and profiles will load custom changes + return true; +} + +void UVRGlobalSettings::PostInitProperties() +{ +#if WITH_EDITOR + // Not doing this currently, loading defaults is cool, and I may go back to it later when i get + // controller offsets for vive/touch/MR vs each other. + /*if (ControllerProfiles.Num() == 0) + { + ControllerProfiles.Add(FBPVRControllerProfile(TEXT("Vive_Wands"))); + ControllerProfiles.Add(FBPVRControllerProfile(TEXT("Oculus_Touch"), ControllerProfileStatics::OculusTouchStaticOffset)); + this->SaveConfig(CPF_Config, *this->GetDefaultConfigFilename()); + }*/ +#endif + + SetScalers(); + + Super::PostInitProperties(); +} + +#if WITH_EDITOR + +void UVRGlobalSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty* PropertyThatChanged = PropertyChangedEvent.Property; + + if (PropertyThatChanged != nullptr) + { +#if WITH_EDITORONLY_DATA + if ( + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, bUseChaosTranslationScalers) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, bSetEngineChaosScalers) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, LinearDriveStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, LinearDriveDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, AngularDriveStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, AngularDriveDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointStiffness) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftLinearStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftLinearDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftAngularStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftAngularDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointLinearBreakScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointAngularBreakScale) + ) + { + SetScalers(); + } +#endif + } +} +#endif + +void UVRGlobalSettings::SetScalers() +{ + auto CVarLinearDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearDriveStiffnessScale")); + auto CVarLinearDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinaearDriveDampingScale")); + auto CVarAngularDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveStiffnessScale")); + auto CVarAngularDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveDampingScale")); + + // Constraint settings + auto CVarJointStiffness = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.JointStiffness")); + auto CVarSoftLinearStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftLinearStiffnessScale")); + auto CVarSoftLinearDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftLinearDampingScale")); + auto CVarSoftAngularStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftAngularStiffnessScale")); + auto CVarSoftAngularDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftAngularDampingScale")); + auto CVarJointLinearBreakScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearBreakScale")); + auto CVarJointAngularBreakScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularBreakScale")); + + if (bUseChaosTranslationScalers && bSetEngineChaosScalers) + { + CVarLinearDriveStiffnessScale->Set(LinearDriveStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarLinearDriveDampingScale->Set(LinearDriveDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveStiffnessScale->Set(AngularDriveStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveDampingScale->Set(AngularDriveDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + + // Constraint settings + CVarJointStiffness->Set(JointStiffness, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearStiffnessScale->Set(SoftLinearStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearDampingScale->Set(SoftLinearDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularStiffnessScale->Set(SoftAngularStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularDampingScale->Set(SoftAngularDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointLinearBreakScale->Set(JointLinearBreakScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointAngularBreakScale->Set(JointAngularBreakScale, EConsoleVariableFlags::ECVF_SetByCode); + } + else if (!bSetEngineChaosScalers) + { + CVarLinearDriveStiffnessScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarLinearDriveDampingScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveStiffnessScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveDampingScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + + // Constraint settings + CVarJointStiffness->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearStiffnessScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearDampingScale->Set(1.2f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularStiffnessScale->Set(100000.f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularDampingScale->Set(1000.f, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointLinearBreakScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointAngularBreakScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + } +} + +void UVRGlobalSettings::GetMeleeSurfaceGlobalSettings(TArray& OutMeleeSurfaceSettings) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + OutMeleeSurfaceSettings = VRSettings.MeleeSurfaceSettings; +} + +void UVRGlobalSettings::GetVirtualStockGlobalSettings(FBPVirtualStockSettings& OutVirtualStockSettings) +{ + const UVRGlobalSettings& VRSettings = *GetDefault(); + + OutVirtualStockSettings.bUseDistanceBasedStockSnapping = VRSettings.VirtualStockSettings.bUseDistanceBasedStockSnapping; + OutVirtualStockSettings.StockSnapDistance = VRSettings.VirtualStockSettings.StockSnapDistance; + OutVirtualStockSettings.StockSnapOffset = VRSettings.VirtualStockSettings.StockSnapOffset; + OutVirtualStockSettings.bSmoothStockHand = VRSettings.VirtualStockSettings.bSmoothStockHand; + OutVirtualStockSettings.SmoothingValueForStock = VRSettings.VirtualStockSettings.SmoothingValueForStock; +} + +void UVRGlobalSettings::SaveVirtualStockGlobalSettings(FBPVirtualStockSettings NewVirtualStockSettings) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault(); + VRSettings.VirtualStockSettings = NewVirtualStockSettings; + + VRSettings.SaveConfig(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp new file mode 100644 index 0000000..242c872 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp @@ -0,0 +1,20 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRGripInterface.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRGripInterface) + +#include "UObject/ObjectMacros.h" +#include "GripScripts/VRGripScriptBase.h" +#include "UObject/Interface.h" + +UVRGripInterface::UVRGripInterface(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + + +void IVRGripInterface::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp new file mode 100644 index 0000000..25593ae --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp @@ -0,0 +1,443 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRPathFollowingComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRPathFollowingComponent) + +//#include "Runtime/Engine/Private/EnginePrivate.h" + +//#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13 +#include "Navigation/MetaNavMeshPath.h" +#include "NavLinkCustomInterface.h" +//#endif + +// Force to use new movement comp + +DEFINE_LOG_CATEGORY(LogPathFollowingVR); + +void UVRPathFollowingComponent::SetMovementComponent(UNavMovementComponent* MoveComp) +{ + Super::SetMovementComponent(MoveComp); + + VRMovementComp = Cast(MovementComp); + + if (VRMovementComp) + { + OnRequestFinished.AddUObject(VRMovementComp, &UVRBaseCharacterMovementComponent::OnMoveCompleted); + } +} + + +void UVRPathFollowingComponent::GetDebugStringTokens(TArray& Tokens, TArray& Flags) const +{ + Tokens.Add(GetStatusDesc()); + Flags.Add(EPathFollowingDebugTokens::Description); + + if (Status != EPathFollowingStatus::Moving) + { + return; + } + + FString& StatusDesc = Tokens[0]; + if (Path.IsValid()) + { + const int32 NumMoveSegments = (Path.IsValid() && Path->IsValid()) ? Path->GetPathPoints().Num() : -1; + const bool bIsDirect = (Path->CastPath() != NULL); + const bool bIsCustomLink = CurrentCustomLinkOb.IsValid(); + + if (!bIsDirect) + { + StatusDesc += FString::Printf(TEXT(" (%d..%d/%d)%s"), MoveSegmentStartIndex + 1, MoveSegmentEndIndex + 1, NumMoveSegments, + bIsCustomLink ? TEXT(" (custom NavLink)") : TEXT("")); + } + else + { + StatusDesc += TEXT(" (direct)"); + } + } + else + { + StatusDesc += TEXT(" (invalid path)"); + } + + // add debug params + float CurrentDot = 0.0f, CurrentDistance = 0.0f, CurrentHeight = 0.0f; + uint8 bFailedDot = 0, bFailedDistance = 0, bFailedHeight = 0; + DebugReachTest(CurrentDot, CurrentDistance, CurrentHeight, bFailedHeight, bFailedDistance, bFailedHeight); + + Tokens.Add(TEXT("dot")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.2f"), CurrentDot)); + Flags.Add(bFailedDot ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); + + Tokens.Add(TEXT("dist2D")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.0f"), CurrentDistance)); + Flags.Add(bFailedDistance ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); + + Tokens.Add(TEXT("distZ")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.0f"), CurrentHeight)); + Flags.Add(bFailedHeight ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); +} + +void UVRPathFollowingComponent::PauseMove(FAIRequestID RequestID, EPathFollowingVelocityMode VelocityMode) +{ + //UE_VLOG(GetOwner(), LogPathFollowing, Log, TEXT("PauseMove: RequestID(%u)"), RequestID); + if (Status == EPathFollowingStatus::Paused) + { + return; + } + + if (RequestID.IsEquivalent(GetCurrentRequestId())) + { + if ((VelocityMode == EPathFollowingVelocityMode::Reset) && MovementComp && HasMovementAuthority()) + { + MovementComp->StopMovementKeepPathing(); + } + + LocationWhenPaused = MovementComp ? (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()) : FVector::ZeroVector; + PathTimeWhenPaused = Path.IsValid() ? Path->GetTimeStamp() : 0.; + Status = EPathFollowingStatus::Paused; + + UpdateMoveFocus(); + } +} + + + +bool UVRPathFollowingComponent::ShouldCheckPathOnResume() const +{ + bool bCheckPath = true; + if (MovementComp != NULL) + { + float AgentRadius = 0.0f, AgentHalfHeight = 0.0f; + MovementComp->GetOwner()->GetSimpleCollisionCylinder(AgentRadius, AgentHalfHeight); + + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocation() : MovementComp->GetActorFeetLocation()); + const FVector::FReal DeltaMove2DSq = (CurrentLocation - LocationWhenPaused).SizeSquared2D(); + const FVector::FReal DeltaZ = FMath::Abs(CurrentLocation.Z - LocationWhenPaused.Z); + if (DeltaMove2DSq < FMath::Square(AgentRadius) && DeltaZ < (AgentHalfHeight * 0.5)) + { + bCheckPath = false; + } + } + + return bCheckPath; +} + +int32 UVRPathFollowingComponent::DetermineStartingPathPoint(const FNavigationPath* ConsideredPath) const +{ + int32 PickedPathPoint = INDEX_NONE; + + if (ConsideredPath && ConsideredPath->IsValid()) + { + // if we already have some info on where we were on previous path + // we can find out if there's a segment on new path we're currently at + if (MoveSegmentStartRef != INVALID_NAVNODEREF && + MoveSegmentEndRef != INVALID_NAVNODEREF && + ConsideredPath->GetNavigationDataUsed() != NULL) + { + // iterate every new path node and see if segment match + for (int32 PathPoint = 0; PathPoint < ConsideredPath->GetPathPoints().Num() - 1; ++PathPoint) + { + if (ConsideredPath->GetPathPoints()[PathPoint].NodeRef == MoveSegmentStartRef && + ConsideredPath->GetPathPoints()[PathPoint + 1].NodeRef == MoveSegmentEndRef) + { + PickedPathPoint = PathPoint; + break; + } + } + } + + if (MovementComp && PickedPathPoint == INDEX_NONE) + { + if (ConsideredPath->GetPathPoints().Num() > 2) + { + // check if is closer to first or second path point (don't assume AI's standing) + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const FVector PathPt0 = *ConsideredPath->GetPathPointLocation(0); + const FVector PathPt1 = *ConsideredPath->GetPathPointLocation(1); + // making this test in 2d to avoid situation where agent's Z location not being in "navmesh plane" + // would influence the result + const FVector::FReal SqDistToFirstPoint = (CurrentLocation - PathPt0).SizeSquared2D(); + const FVector::FReal SqDistToSecondPoint = (CurrentLocation - PathPt1).SizeSquared2D(); + PickedPathPoint = FMath::IsNearlyEqual(SqDistToFirstPoint, SqDistToSecondPoint) ? + ((FMath::Abs(CurrentLocation.Z - PathPt0.Z) < FMath::Abs(CurrentLocation.Z - PathPt1.Z)) ? 0 : 1) : + ((SqDistToFirstPoint < SqDistToSecondPoint) ? 0 : 1); + } + else + { + // If there are only two point we probably should start from the beginning + PickedPathPoint = 0; + } + } + } + + return PickedPathPoint; +} + +bool UVRPathFollowingComponent::UpdateBlockDetection() +{ + const double GameTime = GetWorld()->GetTimeSeconds(); + if (bUseBlockDetection && + MovementComp && + GameTime > (LastSampleTime + BlockDetectionInterval) && + BlockDetectionSampleCount > 0) + { + LastSampleTime = GameTime; + + if (LocationSamples.Num() == NextSampleIdx) + { + LocationSamples.AddZeroed(1); + } + + LocationSamples[NextSampleIdx] = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationBased() : MovementComp->GetActorFeetLocationBased()); + NextSampleIdx = (NextSampleIdx + 1) % BlockDetectionSampleCount; + return true; + } + + return false; +} + +void UVRPathFollowingComponent::UpdatePathSegment() +{ +#if !UE_BUILD_SHIPPING + DEBUG_bMovingDirectlyToGoal = false; +#endif // !UE_BUILD_SHIPPING + + if ((Path.IsValid() == false) || (MovementComp == nullptr)) + { + //UE_CVLOG(Path.IsValid() == false, this, LogPathFollowing, Log, TEXT("Aborting move due to not having a valid path object")); + OnPathFinished(EPathFollowingResult::Aborted, FPathFollowingResultFlags::InvalidPath); + return; + } + + if (!Path->IsValid()) + { + if (!Path->IsWaitingForRepath()) + { + //UE_VLOG(this, LogPathFollowing, Log, TEXT("Aborting move due to path being invelid and not waiting for repath")); + OnPathFinished(EPathFollowingResult::Aborted, FPathFollowingResultFlags::InvalidPath); + } + + return; + } + + FMetaNavMeshPath* MetaNavPath = bIsUsingMetaPath ? Path->CastPath() : nullptr; + + // if agent has control over its movement, check finish conditions + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const bool bCanUpdateState = HasMovementAuthority(); + if (bCanUpdateState && Status == EPathFollowingStatus::Moving) + { + const int32 LastSegmentEndIndex = Path->GetPathPoints().Num() - 1; + const bool bFollowingLastSegment = (MoveSegmentEndIndex >= LastSegmentEndIndex); + const bool bLastPathChunk = (MetaNavPath == nullptr || MetaNavPath->IsLastSection()); + + if (bCollidedWithGoal) + { + // check if collided with goal actor + OnSegmentFinished(); + OnPathFinished(EPathFollowingResult::Success, FPathFollowingResultFlags::None); + } + else if (HasReachedDestination(CurrentLocation)) + { + // always check for destination, acceptance radius may cause it to pass before reaching last segment + OnSegmentFinished(); + OnPathFinished(EPathFollowingResult::Success, FPathFollowingResultFlags::None); + } + else if (bFollowingLastSegment && bMoveToGoalOnLastSegment && bLastPathChunk) + { + // use goal actor for end of last path segment + // UNLESS it's partial path (can't reach goal) + if (DestinationActor.IsValid() && Path->IsPartial() == false) + { + const FVector AgentLocation = DestinationAgent ? DestinationAgent->GetNavAgentLocation() : DestinationActor->GetActorLocation(); + // note that the condition below requires GoalLocation to be in world space. + const FVector GoalLocation = FQuatRotationTranslationMatrix(DestinationActor->GetActorQuat(), AgentLocation).TransformPosition(MoveOffset); + + CurrentDestination.Set(NULL, GoalLocation); + + //UE_VLOG(this, LogPathFollowing, Log, TEXT("Moving directly to move goal rather than following last path segment")); + //UE_VLOG_LOCATION(this, LogPathFollowing, VeryVerbose, GoalLocation, 30, FColor::Green, TEXT("Last-segment-to-actor")); + //UE_VLOG_SEGMENT(this, LogPathFollowing, VeryVerbose, CurrentLocation, GoalLocation, FColor::Green, TEXT_EMPTY); + } + + UpdateMoveFocus(); + +#if !UE_BUILD_SHIPPING + DEBUG_bMovingDirectlyToGoal = true; +#endif // !UE_BUILD_SHIPPING + } + // check if current move segment is finished + else if (HasReachedCurrentTarget(CurrentLocation)) + { + OnSegmentFinished(); + SetNextMoveSegment(); + } + } + + if (bCanUpdateState && Status == EPathFollowingStatus::Moving) + { + // check waypoint switch condition in meta paths + if (MetaNavPath && Status == EPathFollowingStatus::Moving) + { + MetaNavPath->ConditionalMoveToNextSection(CurrentLocation, EMetaPathUpdateReason::MoveTick); + } + + // gather location samples to detect if moving agent is blocked + const bool bHasNewSample = UpdateBlockDetection(); + if (bHasNewSample && IsBlocked()) + { + if (Path->GetPathPoints().IsValidIndex(MoveSegmentEndIndex) && Path->GetPathPoints().IsValidIndex(MoveSegmentStartIndex)) + { + //LogBlockHelper(GetOwner(), MovementComp, MinAgentRadiusPct, MinAgentHalfHeightPct, + //*Path->GetPathPointLocation(MoveSegmentStartIndex), + //*Path->GetPathPointLocation(MoveSegmentEndIndex)); + } + else + { + if ((GetOwner() != NULL) && (MovementComp != NULL)) + { + // UE_VLOG(GetOwner(), LogPathFollowing, Verbose, TEXT("Path blocked, but move segment indices are not valid: start %d, end %d of %d"), MoveSegmentStartIndex, MoveSegmentEndIndex, Path->GetPathPoints().Num()); + } + } + OnPathFinished(EPathFollowingResult::Blocked, FPathFollowingResultFlags::None); + } + } +} + +void UVRPathFollowingComponent::FollowPathSegment(float DeltaTime) +{ + if (!Path.IsValid() || MovementComp == nullptr) + { + return; + } + + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const FVector CurrentTarget = GetCurrentTargetLocation(); + + // set to false by default, we will set set this back to true if appropriate + bIsDecelerating = false; + + const bool bAccelerationBased = MovementComp->UseAccelerationForPathFollowing(); + if (bAccelerationBased) + { + CurrentMoveInput = (CurrentTarget - CurrentLocation).GetSafeNormal(); + + if (bStopMovementOnFinish && (MoveSegmentStartIndex >= DecelerationSegmentIndex)) + { + const FVector PathEnd = Path->GetEndLocation(); + const FVector::FReal DistToEndSq = FVector::DistSquared(CurrentLocation, PathEnd); + const bool bShouldDecelerate = DistToEndSq < FMath::Square(CachedBrakingDistance); + if (bShouldDecelerate) + { + bIsDecelerating = true; + + const FVector::FReal SpeedPct = FMath::Clamp(FMath::Sqrt(DistToEndSq) / CachedBrakingDistance, 0., 1.); + CurrentMoveInput *= SpeedPct; + } + } + + PostProcessMove.ExecuteIfBound(this, CurrentMoveInput); + MovementComp->RequestPathMove(CurrentMoveInput); + } + else + { + FVector MoveVelocity = (CurrentTarget - CurrentLocation) / DeltaTime; + + const int32 LastSegmentStartIndex = Path->GetPathPoints().Num() - 2; + const bool bNotFollowingLastSegment = (MoveSegmentStartIndex < LastSegmentStartIndex); + + PostProcessMove.ExecuteIfBound(this, MoveVelocity); + MovementComp->RequestDirectMove(MoveVelocity, bNotFollowingLastSegment); + } +} + +bool UVRPathFollowingComponent::HasReachedCurrentTarget(const FVector& CurrentLocation) const +{ + if (MovementComp == NULL) + { + return false; + } + + const FVector CurrentTarget = GetCurrentTargetLocation(); + const FVector CurrentDirection = GetCurrentDirection(); + + // check if moved too far + const FVector ToTarget = (CurrentTarget - (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation())); + const FVector::FReal SegmentDot = FVector::DotProduct(ToTarget, CurrentDirection); + if (SegmentDot < 0.) + { + return true; + } + + // or standing at target position + // don't use acceptance radius here, it has to be exact for moving near corners (2D test < 5% of agent radius) + const float GoalRadius = 0.0f; + const float GoalHalfHeight = 0.0f; + + return HasReachedInternal(CurrentTarget, GoalRadius, GoalHalfHeight, CurrentLocation, CurrentAcceptanceRadius, 0.05f); +} + +void UVRPathFollowingComponent::DebugReachTest(float& CurrentDot, float& CurrentDistance, float& CurrentHeight, uint8& bDotFailed, uint8& bDistanceFailed, uint8& bHeightFailed) const +{ + if (!Path.IsValid() || MovementComp == NULL) + { + return; + } + + const int32 LastSegmentEndIndex = Path->GetPathPoints().Num() - 1; + const bool bFollowingLastSegment = (MoveSegmentEndIndex >= LastSegmentEndIndex); + + float GoalRadius = 0.0f; + float GoalHalfHeight = 0.0f; + float RadiusThreshold = 0.0f; + float AgentRadiusPct = 0.05f; + + FVector AgentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + FVector GoalLocation = GetCurrentTargetLocation(); + RadiusThreshold = CurrentAcceptanceRadius; + + if (bFollowingLastSegment) + { + GoalLocation = *Path->GetPathPointLocation(Path->GetPathPoints().Num() - 1); + AgentRadiusPct = MinAgentRadiusPct; + + // take goal's current location, unless path is partial or last segment doesn't reach goal actor (used by tethered AI) + if (DestinationActor.IsValid() && !Path->IsPartial() && bMoveToGoalOnLastSegment) + { + if (DestinationAgent) + { + FVector GoalOffset; + + const AActor* OwnerActor = GetOwner(); + DestinationAgent->GetMoveGoalReachTest(OwnerActor, MoveOffset, GoalOffset, GoalRadius, GoalHalfHeight); + GoalLocation = FQuatRotationTranslationMatrix(DestinationActor->GetActorQuat(), DestinationAgent->GetNavAgentLocation()).TransformPosition(GoalOffset); + } + else + { + GoalLocation = DestinationActor->GetActorLocation(); + } + } + } + + const FVector ToGoal = (GoalLocation - AgentLocation); + const FVector CurrentDirection = GetCurrentDirection(); + CurrentDot = FloatCastChecked(FVector::DotProduct(ToGoal.GetSafeNormal(), CurrentDirection), /* Precision */ 1. / 128.); + bDotFailed = (CurrentDot < 0.0f) ? 1 : 0; + + // get cylinder of moving agent + float AgentRadius = 0.0f; + float AgentHalfHeight = 0.0f; + AActor* MovingAgent = MovementComp->GetOwner(); + MovingAgent->GetSimpleCollisionCylinder(AgentRadius, AgentHalfHeight); + + CurrentDistance = FloatCastChecked(ToGoal.Size2D(), UE::LWC::DefaultFloatPrecision); + const float UseRadius = FMath::Max(RadiusThreshold, GoalRadius + (AgentRadius * AgentRadiusPct)); + bDistanceFailed = (CurrentDistance > UseRadius) ? 1 : 0; + + CurrentHeight = FloatCastChecked(FMath::Abs(ToGoal.Z), UE::LWC::DefaultFloatPrecision); + const float UseHeight = GoalHalfHeight + (AgentHalfHeight * MinAgentHalfHeightPct); + bHeightFailed = (CurrentHeight > UseHeight) ? 1 : 0; +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp new file mode 100644 index 0000000..39eebe0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp @@ -0,0 +1,122 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRPlayerController.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRPlayerController) + +#include "AI/NavigationSystemBase.h" +#include "VRBaseCharacterMovementComponent.h" +#include "VRPathFollowingComponent.h" +//#include "VRBPDatatypes.h" +#include "Engine/Player.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + + +AVRPlayerController::AVRPlayerController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bDisableServerUpdateCamera = true; +} + +void AVRPlayerController::SpawnPlayerCameraManager() +{ + Super::SpawnPlayerCameraManager(); + + // Turn off the default FOV and position replication of the camera manager, most functions should be sending values anyway and I am replicating + // the actual camera position myself so this is just wasted bandwidth + if(PlayerCameraManager != NULL && bDisableServerUpdateCamera) + PlayerCameraManager->bUseClientSideCameraUpdates = false; +} + +// #TODO 4.20: This was removed +/*void AVRPlayerController::InitNavigationControl(UPathFollowingComponent*& PathFollowingComp) +{ + PathFollowingComp = FindComponentByClass(); + if (PathFollowingComp == NULL) + { + PathFollowingComp = NewObject(this); + PathFollowingComp->RegisterComponentWithWorld(GetWorld()); + PathFollowingComp->Initialize(); + } +}*/ + +/*IPathFollowingAgentInterface* AVRPlayerController::GetPathFollowingAgent() const +{ + // Moved spawning the path following component into the path finding logic instead + return FNavigationSystem::FindPathFollowingAgentForActor(*this); +}*/ + +void AVRPlayerController::PlayerTick(float DeltaTime) +{ + + // #TODO: Should I be only doing this if ticking CMC and CMC is active? + if (AVRBaseCharacter * VRChar = Cast(GetPawn())) + { + // Keep from calling multiple times + UVRBaseCharacterMovementComponent * BaseCMC = Cast(VRChar->GetMovementComponent()); + + if (!BaseCMC || !BaseCMC->bRunControlRotationInMovementComponent) + return Super::PlayerTick(DeltaTime); + + if (!bShortConnectTimeOut) + { + bShortConnectTimeOut = true; + ServerShortTimeout(); + } + + TickPlayerInput(DeltaTime, DeltaTime == 0.f); + LastRotationInput = RotationInput; + + if ((Player != NULL) && (Player->PlayerController == this)) + { + // Validate current state + bool bUpdateRotation = false; + if (IsInState(NAME_Playing)) + { + if (GetPawn() == NULL) + { + ChangeState(NAME_Inactive); + } + else if (Player && GetPawn() == AcknowledgedPawn && (!BaseCMC || (BaseCMC && !BaseCMC->IsActive()))) + { + bUpdateRotation = true; + } + } + + if (IsInState(NAME_Inactive)) + { + if (GetLocalRole() < ROLE_Authority) + { + SafeServerCheckClientPossession(); + } + + //bUpdateRotation = !IsFrozen(); + } + else if (IsInState(NAME_Spectating)) + { + if (GetLocalRole() < ROLE_Authority) + { + SafeServerUpdateSpectatorState(); + } + + // Keep it when spectating + bUpdateRotation = true; + } + + // Update rotation + if (bUpdateRotation) + { + UpdateRotation(DeltaTime); + } + } + } + else + { + // Not our character, forget it + Super::PlayerTick(DeltaTime); + } +} + +UVRLocalPlayer::UVRLocalPlayer(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp new file mode 100644 index 0000000..eaad423 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp @@ -0,0 +1,1719 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRRootComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRRootComponent) + +//#include "Runtime/Engine/Private/EnginePrivate.h" +//#include "WorldCollision.h" +#include "PhysicsPublic.h" +#include "Engine/ScopedMovementUpdate.h" +//#include "DrawDebugHelpers.h" +#include "IHeadMountedDisplay.h" +#include "IXRTrackingSystem.h" +#include "VRCharacter.h" +#include "Algo/Copy.h" + +#include "Components/PrimitiveComponent.h" + +DEFINE_LOG_CATEGORY(LogVRRootComponent); +#define LOCTEXT_NAMESPACE "VRRootComponent" + +DECLARE_CYCLE_STAT(TEXT("VRRootMovement"), STAT_VRRootMovement, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("PerformOverlapQueryVR Time"), STAT_PerformOverlapQueryVR, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("UpdateOverlapsVRRoot Time"), STAT_UpdateOverlapsVRRoot, STATGROUP_VRRootComponent); + +typedef TArray> TInlineOverlapPointerArray; + +// Helper to see if two components can possibly generate overlaps with each other. +FORCEINLINE_DEBUGGABLE static bool CanComponentsGenerateOverlap(const UPrimitiveComponent* MyComponent, /*const*/ UPrimitiveComponent* OtherComp) +{ + return OtherComp + && OtherComp->GetGenerateOverlapEvents() + && MyComponent + && MyComponent->GetGenerateOverlapEvents() + && MyComponent->GetCollisionResponseToComponent(OtherComp) == ECR_Overlap; +} + +// Predicate to identify components from overlaps array that can overlap +struct FPredicateFilterCanOverlap +{ + FPredicateFilterCanOverlap(const UPrimitiveComponent& OwningComponent) + : MyComponent(OwningComponent) + { + } + + bool operator() (const FOverlapInfo& Info) const + { + return CanComponentsGenerateOverlap(&MyComponent, Info.OverlapInfo.GetComponent()); + } + +private: + const UPrimitiveComponent& MyComponent; +}; + +// Predicate to remove components from overlaps array that can no longer overlap +struct FPredicateFilterCannotOverlap +{ + FPredicateFilterCannotOverlap(const UPrimitiveComponent& OwningComponent) + : MyComponent(OwningComponent) + { + } + + bool operator() (const FOverlapInfo& Info) const + { + return !CanComponentsGenerateOverlap(&MyComponent, Info.OverlapInfo.GetComponent()); + } + +private: + const UPrimitiveComponent& MyComponent; +}; + +// Helper to initialize an array to point to data members in another array. +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray& Pointers, const TArray& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i = 0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray& Pointers, const TArrayView& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i = 0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +// Helper to initialize an array to point to data members in another array which satisfy a predicate. +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray& Pointers, const TArray& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} + +template +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray& Pointers, const TArrayView& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} + +// LOOKING_FOR_PERF_ISSUES +#define PERF_MOVECOMPONENT_STATS 0 + +namespace PrimitiveComponentStatics +{ + //static const FText MobilityWarnText = LOCTEXT("InvalidMove", "move"); + static const FName MoveComponentName(TEXT("MoveComponent")); + static const FName UpdateOverlapsName(TEXT("UpdateOverlaps")); +} + +// Helper for finding the index of an FOverlapInfo in an Array using the FFastOverlapInfoCompare predicate, knowing that at least one overlap is valid (non-null). +template +FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray& OverlapArray, const FOverlapInfo& SearchItem) +{ + return OverlapArray.IndexOfByPredicate(FFastOverlapInfoCompare(SearchItem)); +} + +// Version that works with arrays of pointers and pointers to search items. +template +FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray& OverlapPtrArray, const FOverlapInfo* SearchItem) +{ + return OverlapPtrArray.IndexOfByPredicate(FFastOverlapInfoCompare(*SearchItem)); +} + +// Helper for adding an FOverlapInfo uniquely to an Array, using IndexOfOverlapFast and knowing that at least one overlap is valid (non-null). +template +FORCEINLINE_DEBUGGABLE void AddUniqueOverlapFast(TArray& OverlapArray, FOverlapInfo& NewOverlap) +{ + if (IndexOfOverlapFast(OverlapArray, NewOverlap) == INDEX_NONE) + { + OverlapArray.Add(NewOverlap); + } +} + +template +FORCEINLINE_DEBUGGABLE void AddUniqueOverlapFast(TArray& OverlapArray, FOverlapInfo&& NewOverlap) +{ + if (IndexOfOverlapFast(OverlapArray, NewOverlap) == INDEX_NONE) + { + OverlapArray.Add(NewOverlap); + } +} + +static void PullBackHit(FHitResult& Hit, const FVector& Start, const FVector& End, const float Dist) +{ + const float DesiredTimeBack = FMath::Clamp(0.1f, 0.1f / Dist, 1.f / Dist) + 0.001f; + Hit.Time = FMath::Clamp(Hit.Time - DesiredTimeBack, 0.f, 1.f); +} + +static bool ShouldIgnoreHitResult(const UWorld* InWorld, bool bAllowSimulatingCollision, FHitResult const& TestHit, FVector const& MovementDirDenormalized, const AActor* MovingActor, EMoveComponentFlags MoveFlags) +{ + if (TestHit.bBlockingHit) + { + // VR Pawns need to totally ignore simulating components with movement to prevent sickness + if (!bAllowSimulatingCollision && TestHit.Component.IsValid() && TestHit.Component->IsSimulatingPhysics()) + return true; + + // check "ignore bases" functionality + if ((MoveFlags & MOVECOMP_IgnoreBases) && MovingActor) //we let overlap components go through because their overlap is still needed and will cause beginOverlap/endOverlap events + { + // ignore if there's a base relationship between moving actor and hit actor + AActor const* const HitActor = TestHit.HitObjectHandle.FetchActor(); + if (HitActor) + { + if (MovingActor->IsBasedOnActor(HitActor) || HitActor->IsBasedOnActor(MovingActor)) + { + return true; + } + } + } + + // If we started penetrating, we may want to ignore it if we are moving out of penetration. + // This helps prevent getting stuck in walls. + static const auto CVarHitDistanceTolerance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.HitDistanceTolerance")); + if ((TestHit.Distance < CVarHitDistanceTolerance->GetFloat() || TestHit.bStartPenetrating) && !(MoveFlags & MOVECOMP_NeverIgnoreBlockingOverlaps)) + { + static const auto CVarInitialOverlapTolerance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.InitialOverlapTolerance")); + const float DotTolerance = CVarInitialOverlapTolerance->GetFloat(); + + // Dot product of movement direction against 'exit' direction + const FVector MovementDir = MovementDirDenormalized.GetSafeNormal(); + const float MoveDot = (TestHit.ImpactNormal | MovementDir); + + const bool bMovingOut = MoveDot > DotTolerance; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + static const auto CVarShowInitialOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ShowInitialOverlaps")); + if (CVarShowInitialOverlaps->GetInt() != 0) + { + UE_LOG(LogVRRootComponent, Log, TEXT("Overlapping %s Dir %s Dot %f Normal %s Depth %f"), *GetNameSafe(TestHit.Component.Get()), *MovementDir.ToString(), MoveDot, *TestHit.ImpactNormal.ToString(), TestHit.PenetrationDepth); + DrawDebugDirectionalArrow(InWorld, TestHit.TraceStart, TestHit.TraceStart + 30.f * TestHit.ImpactNormal, 5.f, bMovingOut ? FColor(64, 128, 255) : FColor(255, 64, 64), true, 4.f); + if (TestHit.PenetrationDepth > UE_KINDA_SMALL_NUMBER) + { + DrawDebugDirectionalArrow(InWorld, TestHit.TraceStart, TestHit.TraceStart + TestHit.PenetrationDepth * TestHit.Normal, 5.f, FColor(64, 255, 64), true, 4.f); + } + } + // } +#endif + + // If we are moving out, ignore this result! + if (bMovingOut) + { + return true; + } + } + } + + return false; +} +static FORCEINLINE_DEBUGGABLE bool ShouldIgnoreOverlapResult(const UWorld* World, const AActor* ThisActor, const UPrimitiveComponent& ThisComponent, const AActor* OtherActor, const UPrimitiveComponent& OtherComponent, bool bCheckOverlapFlags) +{ + // Don't overlap with self + if (&ThisComponent == &OtherComponent) + { + return true; + } + + if (bCheckOverlapFlags) + { + // Both components must set GetGenerateOverlapEvents() + if (!ThisComponent.GetGenerateOverlapEvents() || !OtherComponent.GetGenerateOverlapEvents()) + { + return true; + } + } + + if (!ThisActor || !OtherActor) + { + return true; + } + + if (!World || OtherActor == (AActor*)World->GetWorldSettings() || !OtherActor->IsActorInitialized()) + { + return true; + } + + return false; +} + + +UVRRootComponent::UVRRootComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + bWantsInitializeComponent = true; + + this->SetRelativeScale3D(FVector(1.f)); + this->SetRelativeLocation(FVector::ZeroVector); + + // 2.15f is ((MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2), same value that walking attempts to retain + // 1.9f is MIN_FLOOR_DIST, this would not go below ledges when hanging off + VRCapsuleOffset = FVector(-8.0f, 0.0f, 2.15f /*0.0f*/); + + bCenterCapsuleOnHMD = false; + bPauseTracking = false; + + + ShapeColor = FColor(223, 149, 157, 255); + + CapsuleRadius = 20.0f; + CapsuleHalfHeight = 96.0f; + bUseEditorCompositing = true; + OffsetComponentToWorld = FTransform(FQuat(0.0f,0.0f,0.0f,1.0f), FVector::ZeroVector, FVector(1.0f)); + + // Fixes a problem where headset stays at 0,0,0 + lastCameraLoc = FVector::ZeroVector; + lastCameraRot = FRotator::ZeroRotator; + curCameraRot = FRotator::ZeroRotator; + curCameraLoc = FVector::ZeroVector; + StoredCameraRotOffset = FRotator::ZeroRotator; + TargetPrimitiveComponent = NULL; + owningVRChar = NULL; + //VRCameraCollider = NULL; + + bAllowSimulatingCollision = false; + bUseWalkingCollisionOverride = false; + WalkingCollisionOverride = ECollisionChannel::ECC_Pawn; + + bCalledUpdateTransform = false; + + CanCharacterStepUpOn = ECB_No; + //bShouldUpdatePhysicsVolume = true; +// bCheckAsyncSceneOnMove = false; + SetCanEverAffectNavigation(false); + bDynamicObstacle = true; + + //bOffsetByHMD = false; +} + +/** Represents a UVRRootComponent to the scene manager. */ +class FDrawVRCylinderSceneProxy final : public FPrimitiveSceneProxy +{ +public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + FDrawVRCylinderSceneProxy(const UVRRootComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected) + , CapsuleRadius(InComponent->GetScaledCapsuleRadius()) + , CapsuleHalfHeight(InComponent->GetScaledCapsuleHalfHeight()) + , ShapeColor(InComponent->ShapeColor) + , VRCapsuleOffset(InComponent->VRCapsuleOffset) + , bSimulating(false) + //, OffsetComponentToWorld(InComponent->OffsetComponentToWorld) + , LocalToWorld(InComponent->OffsetComponentToWorld.ToMatrixWithScale()) + { + bWillEverBeLit = false; + } + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements_DrawDynamicElements); + + //const FMatrix& LocalToWorld = OffsetComponentToWorld.ToMatrixWithScale();//GetLocalToWorld(); + const int32 CapsuleSides = FMath::Clamp(CapsuleRadius / 4.f, 16, 64); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + const FLinearColor DrawCapsuleColor = GetViewSelectionColor(ShapeColor, *View, IsSelected(), IsHovered(), false, IsIndividuallySelected()); + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + + // If in editor views, lets offset the capsule upwards so that it views correctly + if (bSimulating) + { + DrawWireCapsule(PDI, LocalToWorld.GetOrigin() - FVector(0.f, 0.f, CapsuleHalfHeight), LocalToWorld.GetUnitAxis(EAxis::X), LocalToWorld.GetUnitAxis(EAxis::Y), LocalToWorld.GetUnitAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World); + } + else if (UseEditorCompositing(View)) + { + DrawWireCapsule(PDI, LocalToWorld.GetOrigin() /*+ FVector(0.f, 0.f, CapsuleHalfHeight)*/, LocalToWorld.GetUnitAxis(EAxis::X), LocalToWorld.GetUnitAxis(EAxis::Y), LocalToWorld.GetUnitAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World, 1.25f); + } + else + DrawWireCapsule(PDI, LocalToWorld.GetOrigin(), LocalToWorld.GetUnitAxis(EAxis::X), LocalToWorld.GetUnitAxis(EAxis::Y), LocalToWorld.GetUnitAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World, 1.25f); + } + } + } + + /** Called on render thread to assign new dynamic data */ + void UpdateTransform_RenderThread(const FTransform &NewTransform, float NewHalfHeight, bool bIsSimulating) + { + check(IsInRenderingThread()); + LocalToWorld = NewTransform.ToMatrixWithScale(); + //OffsetComponentToWorld = NewTransform; + CapsuleHalfHeight = NewHalfHeight; + bSimulating = bIsSimulating; + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + const bool bProxyVisible = !bDrawOnlyIfSelected || IsSelected(); + + // Should we draw this because collision drawing is enabled, and we have collision + const bool bShowForCollision = View->Family->EngineShowFlags.Collision && IsCollisionEnabled(); + + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = (IsShown(View) && bProxyVisible) || bShowForCollision; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + const uint32 bDrawOnlyIfSelected : 1; + const float CapsuleRadius; + float CapsuleHalfHeight; + FColor ShapeColor; + const FVector VRCapsuleOffset; + bool bSimulating = false; + //FTransform OffsetComponentToWorld; + FMatrix LocalToWorld; +}; + +FPrimitiveSceneProxy* UVRRootComponent::CreateSceneProxy() +{ + return new FDrawVRCylinderSceneProxy(this); +} + +void UVRRootComponent::InitializeComponent() +{ + Super::InitializeComponent(); + GenerateOffsetToWorld(); +} + +void UVRRootComponent::BeginPlay() +{ + Super::BeginPlay(); + + if(AVRBaseCharacter * vrOwner = Cast(this->GetOwner())) + { + TargetPrimitiveComponent = vrOwner->VRReplicatedCamera; + owningVRChar = vrOwner; + //VRCameraCollider = vrOwner->VRCameraCollider; + return; + } + else + { + TArray children = this->GetAttachChildren(); + + for (int i = 0; i < children.Num(); i++) + { + if (children[i]->IsA(UCameraComponent::StaticClass())) + { + TargetPrimitiveComponent = children[i]; + owningVRChar = NULL; + return; + } + } + } + + //VRCameraCollider = NULL; + TargetPrimitiveComponent = NULL; + owningVRChar = NULL; +} + +void UVRRootComponent::SetTrackingPaused(bool bPaused) +{ + bPauseTracking = bPaused; +} + +void UVRRootComponent::UpdateCharacterCapsuleOffset() +{ + if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + if (!FMath::IsNearlyEqual(LastCapsuleHalfHeight, CapsuleHalfHeight)) + { + owningVRChar->NetSmoother->SetRelativeLocation(GetTargetHeightOffset(), false, nullptr, ETeleportType::TeleportPhysics); + + // Update our last sample value + LastCapsuleHalfHeight = CapsuleHalfHeight; + } + } +} + +void UVRRootComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + if (this->IsSimulatingPhysics()) + { + return Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } + + // Skip updates and stay in place if we have paused tracking to the HMD + if (bPauseTracking) + { + bHadRelativeMovement = false; + DifferenceFromLastFrame = FVector::ZeroVector; + return; + } + + UVRBaseCharacterMovementComponent * CharMove = nullptr; + + // Need these for passing physics updates to character movement + if (IsValid(owningVRChar)) + { + CharMove = Cast(owningVRChar->GetCharacterMovement()); + } + + if (IsLocallyControlled()) + { + if (owningVRChar && owningVRChar->bTrackingPaused) + { + curCameraLoc = owningVRChar->PausedTrackingLoc; + curCameraRot = FRotator(0.f, owningVRChar->PausedTrackingRot, 0.f); + } + else if (OptionalWaistTrackingParent.IsValid()) + { + FTransform NewTrans = IVRTrackedParentInterface::Default_GetWaistOrientationAndPosition(OptionalWaistTrackingParent); + curCameraLoc = NewTrans.GetTranslation(); + curCameraRot = NewTrans.Rotator(); + } + else if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot; + if (!GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc)) + { + curCameraLoc = lastCameraLoc; + curCameraRot = lastCameraRot; + } + else + { + if (owningVRChar && owningVRChar->VRReplicatedCamera) + { + owningVRChar->VRReplicatedCamera->ApplyTrackingParameters(curCameraLoc, true); + } + + curCameraRot = curRot.Rotator(); + } + } + else if (TargetPrimitiveComponent) + { + curCameraRot = TargetPrimitiveComponent->GetRelativeRotation(); + curCameraLoc = TargetPrimitiveComponent->GetRelativeLocation(); + } + else + { + curCameraRot = FRotator::ZeroRotator; + curCameraLoc = FVector::ZeroVector; + } + + // Store a leveled yaw value here so it is only calculated once + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + + // Skip this if not retaining roomscale as we use higher initial fidelity + // And we can rep the values at full precision if we want too + if (owningVRChar->bRetainRoomscale) + { + // Pre-Process this for network sends + curCameraLoc.X = FMath::RoundToFloat(curCameraLoc.X * 100.f) / 100.f; + curCameraLoc.Y = FMath::RoundToFloat(curCameraLoc.Y * 100.f) / 100.f; + curCameraLoc.Z = FMath::RoundToFloat(curCameraLoc.Z * 100.f) / 100.f; + } + + + // Can adjust the relative tolerances to remove jitter and some update processing + if (!owningVRChar->bRetainRoomscale || (!curCameraLoc.Equals(lastCameraLoc, 0.01f) || !curCameraRot.Equals(lastCameraRot, 0.01f))) + { + // Also calculate vector of movement for the movement component + FVector LastPosition = OffsetComponentToWorld.GetLocation(); + + bCalledUpdateTransform = false; + + if (owningVRChar->bRetainRoomscale) + { + // If the character movement doesn't exist or is not active/ticking + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive()) + { + OnUpdateTransform(EUpdateTransformFlags::None, ETeleportType::None); + } + else // Let the character movement move the capsule instead + { + // Skip physics update, let the movement component handle it instead + OnUpdateTransform(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + } + + FHitResult OutHit; + FCollisionQueryParams Params("RelativeMovementSweep", false, GetOwner()); + FCollisionResponseParams ResponseParam; + + InitSweepCollisionParams(Params, ResponseParam); + Params.bFindInitialOverlaps = true; + bool bBlockingHit = false; + + if (bUseWalkingCollisionOverride && owningVRChar->bRetainRoomscale) + { + FVector TargetWorldLocation = OffsetComponentToWorld.GetLocation(); + bool bAllowWalkingCollision = false; + if (CharMove != nullptr) + { + if (CharMove->MovementMode == EMovementMode::MOVE_Walking || CharMove->MovementMode == EMovementMode::MOVE_NavWalking) + bAllowWalkingCollision = true; + } + + // TODO: Needs not retained roomscale version that uses movement diff instead of offset to world + if (bAllowWalkingCollision) + { + bBlockingHit = GetWorld()->SweepSingleByChannel(OutHit, LastPosition, TargetWorldLocation, FQuat::Identity, WalkingCollisionOverride, GetCollisionShape(), Params, ResponseParam); + } + + if (bBlockingHit && OutHit.Component.IsValid()) + { + if (CharMove != nullptr && CharMove->bIgnoreSimulatingComponentsInFloorCheck && OutHit.Component->IsSimulatingPhysics()) + bHadRelativeMovement = false; + else + bHadRelativeMovement = true; + } + else + bHadRelativeMovement = false; + } + else + bHadRelativeMovement = true; + + // Not supporting walking collision override currently with new pawn setup + if (bHadRelativeMovement || !owningVRChar->bRetainRoomscale) + { + if (owningVRChar->bRetainRoomscale) + { + DifferenceFromLastFrame = OffsetComponentToWorld.GetLocation() - LastPosition; + lastCameraLoc = curCameraLoc; + lastCameraRot = curCameraRot; + + //DifferenceFromLastFrame = (NextTransform.GetLocation() - LastPosition);// .GetSafeNormal2D(); + DifferenceFromLastFrame.X = FMath::RoundToFloat(DifferenceFromLastFrame.X * 100.f) / 100.f; + DifferenceFromLastFrame.Y = FMath::RoundToFloat(DifferenceFromLastFrame.Y * 100.f) / 100.f; + DifferenceFromLastFrame.Z = FMath::RoundToFloat(DifferenceFromLastFrame.Z * 100.f) / 100.f; + //DifferenceFromLastFrame.Z = 0.0f; // Reset Z to zero, its not used anyway and this lets me reuse the Z component for capsule half height + } + else + { + + // Run this first so we get full fidelity on the relative space calculation + FVector NewLocation = StoredCameraRotOffset.RotateVector(FVector(VRCapsuleOffset.X, VRCapsuleOffset.Y, 0.0f)) + curCameraLoc; + FVector PlanerLocation = NewLocation - lastCameraLoc; + PlanerLocation.Z = 0.0f; + DifferenceFromLastFrame = GetComponentTransform().TransformVector(PlanerLocation); + lastCameraLoc = NewLocation; + lastCameraRot = curCameraRot; + + // Skip routing the delta if this is the first tick, prevents an initial jump in position + if (!bTickedOnce) + { + DifferenceFromLastFrame = FVector::ZeroVector; + bTickedOnce = true; + } + else + { + // I'm limiting it to a -100 - 100 unit range in case people somehow skip tracking so far that this multiplication + // Would overflow the max/min values of a float. It shouldn't ever be harmful as that is a loooot of travel in one tick + DifferenceFromLastFrame.X = FMath::RoundToFloat(FMath::Clamp(DifferenceFromLastFrame.X, -100.0f, 100.0f) * 10000.f) / 10000.f; + DifferenceFromLastFrame.Y = FMath::RoundToFloat(FMath::Clamp(DifferenceFromLastFrame.Y, -100.0f, 100.0f) * 10000.f) / 10000.f; + DifferenceFromLastFrame.Z = FMath::RoundToFloat(FMath::Clamp(DifferenceFromLastFrame.Z, -100.0f, 100.0f) * 10000.f) / 10000.f; + //DifferenceFromLastFrame.Z = 0.0f; // Reset Z to zero, its not used anyway and this lets me reuse the Z component for capsule half height + } + } + + } + else // Zero it out so we don't process off of the change (multiplayer sends this) + { + DifferenceFromLastFrame = FVector::ZeroVector; + lastCameraLoc = curCameraLoc; + lastCameraRot = curCameraRot; + } + } + else + { + bHadRelativeMovement = false; + DifferenceFromLastFrame = FVector::ZeroVector; + lastCameraLoc = curCameraLoc; + lastCameraRot = curCameraRot; + } + + //lastCameraLoc = curCameraLoc; + //lastCameraRot = curCameraRot; + } + else + { + if (owningVRChar && owningVRChar->bTrackingPaused) + { + curCameraLoc = owningVRChar->PausedTrackingLoc; + curCameraRot = FRotator(0.f, owningVRChar->PausedTrackingRot, 0.f); + } + else if (TargetPrimitiveComponent) + { + curCameraRot = TargetPrimitiveComponent->GetRelativeRotation(); + curCameraLoc = TargetPrimitiveComponent->GetRelativeLocation(); + } + else + { + curCameraRot = FRotator(0.0f, 0.0f, 0.0f);// = FRotator::ZeroRotator; + curCameraLoc = FVector(0.0f, 0.0f, 0.0f);//FVector::ZeroVector; + } + + // Store a leveled yaw value here so it is only calculated once + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + + // Can adjust the relative tolerances to remove jitter and some update processing + if (!curCameraLoc.Equals(lastCameraLoc, 0.01f) || !curCameraRot.Equals(lastCameraRot, 0.01f)) + { + bCalledUpdateTransform = false; + + // If the character movement doesn't exist or is not active/ticking + if (!CharMove || !CharMove->IsActive()) + { + OnUpdateTransform(EUpdateTransformFlags::None, ETeleportType::None); + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + else // Let the character movement move the capsule instead + { + // Skip physics update, let the movement component handle it instead + OnUpdateTransform(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + + // This is an edge case, need to check if the nav data needs updated client side + if (this->GetOwner()->GetLocalRole() == ENetRole::ROLE_SimulatedProxy) + { + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + } + + + lastCameraRot = curCameraRot; + lastCameraLoc = curCameraLoc; + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + + +void UVRRootComponent::SendPhysicsTransform(ETeleportType Teleport) +{ + /*if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + Super::SendPhysicsTransform(Teleport); + return; + }*/ + + BodyInstance.SetBodyTransform(OffsetComponentToWorld, Teleport); + BodyInstance.UpdateBodyScale(OffsetComponentToWorld.GetScale3D()); +} + +void UVRRootComponent::SetSimulatePhysics(bool bSimulate) +{ + Super::SetSimulatePhysics(bSimulate); + + if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + return Super::SetSimulatePhysics(bSimulate); + } + + if (bSimulate) + { + if (AVRCharacter* OwningCharacter = Cast(GetOwner())) + { + OwningCharacter->NetSmoother->SetRelativeLocation(FVector(0.f,0.f, -this->GetUnscaledCapsuleHalfHeight())); + } + this->AddWorldOffset(this->GetComponentRotation().RotateVector(FVector(0.f, 0.f, this->GetScaledCapsuleHalfHeight())), false, nullptr, ETeleportType::TeleportPhysics); + } + else + { + if (AVRCharacter* OwningCharacter = Cast(GetOwner())) + { + OwningCharacter->NetSmoother->SetRelativeLocation(FVector(0.f, 0.f, 0)); + } + this->AddWorldOffset(this->GetComponentRotation().RotateVector(FVector(0.f, 0.f, -this->GetScaledCapsuleHalfHeight())), false, nullptr, ETeleportType::TeleportPhysics); + } +} + +// Override this so that the physics representation is in the correct location +void UVRRootComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + if (this->IsSimulatingPhysics()) + { + if (this->ShouldRender() && this->SceneProxy) + { + FTransform lOffsetComponentToWorld = OffsetComponentToWorld; + float lCapsuleHalfHeight = CapsuleHalfHeight; + bool bIsSimulating = this->IsSimulatingPhysics(); + FDrawVRCylinderSceneProxy* CylinderSceneProxy = (FDrawVRCylinderSceneProxy*)SceneProxy; + ENQUEUE_RENDER_COMMAND(VRRootComponent_SendNewDebugTransform)( + [CylinderSceneProxy, lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating](FRHICommandList& RHICmdList) + { + CylinderSceneProxy->UpdateTransform_RenderThread(lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating); + }); + } + + return Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + } + + GenerateOffsetToWorld(); + // Using the physics flag for all of this anyway, no reason for a custom flag, it handles it fine + if (!(UpdateTransformFlags & EUpdateTransformFlags::SkipPhysicsUpdate)) + { + bCalledUpdateTransform = true; + + // Just using the + if (this->ShouldRender() && this->SceneProxy) + { + /*ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( + FDrawCylinderTransformUpdate, + FDrawVRCylinderSceneProxy*, CylinderSceneProxy, (FDrawVRCylinderSceneProxy*)SceneProxy, + FTransform, OffsetComponentToWorld, OffsetComponentToWorld, float, CapsuleHalfHeight, CapsuleHalfHeight, + { + CylinderSceneProxy->UpdateTransform_RenderThread(OffsetComponentToWorld, CapsuleHalfHeight); + } + );*/ + + FTransform lOffsetComponentToWorld = OffsetComponentToWorld; + float lCapsuleHalfHeight = CapsuleHalfHeight; + bool bIsSimulating = this->IsSimulatingPhysics(); + FDrawVRCylinderSceneProxy* CylinderSceneProxy = (FDrawVRCylinderSceneProxy*)SceneProxy; + ENQUEUE_RENDER_COMMAND(VRRootComponent_SendNewDebugTransform)( + [CylinderSceneProxy, lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating](FRHICommandList& RHICmdList) + { + CylinderSceneProxy->UpdateTransform_RenderThread(lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating); + }); + + } + + // Don't want to call primitives version, and the scenecomponents version does nothing + //Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + // Always send new transform to physics + if (bPhysicsStateCreated) + { + //If we update transform of welded bodies directly (i.e. on the actual component) we need to update the shape transforms of the parent. + //If the parent is updated, any welded shapes are automatically updated so we don't need to do this physx update. + //If the parent is updated and we are NOT welded, the child still needs to update physics + const bool bTransformSetDirectly = !(UpdateTransformFlags & EUpdateTransformFlags::PropagateFromParent); + if (bTransformSetDirectly || !IsWelded()) + { + SendPhysicsTransform(Teleport); + } + } + } +} + +FBoxSphereBounds UVRRootComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + + if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + return Super::CalcBounds(LocalToWorld); + } + + FVector BoxPoint = FVector(CapsuleRadius, CapsuleRadius, CapsuleHalfHeight); + //FRotator CamRotOffset(0.0f, curCameraRot.Yaw, 0.0f); + + //FRotator CamRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw(curCameraRot); + /*if(bOffsetByHMD) + return FBoxSphereBounds(FVector(0, 0, CapsuleHalfHeight) + StoredCameraRotOffset.RotateVector(VRCapsuleOffset), BoxPoint, BoxPoint.Size()).TransformBy(LocalToWorld); + else*/ + + return FBoxSphereBounds(FVector(curCameraLoc.X, curCameraLoc.Y, CapsuleHalfHeight) + StoredCameraRotOffset.RotateVector(VRCapsuleOffset), BoxPoint, BoxPoint.Size()).TransformBy(LocalToWorld); +} + +void UVRRootComponent::GetNavigationData(FNavigationRelevantData& Data) const +{ + if (bDynamicObstacle) + { + Data.Modifiers.CreateAreaModifiers(this, GetDesiredAreaClass()); + } +} + +#if WITH_EDITOR +void UVRRootComponent::PreEditChange(FProperty* PropertyThatWillChange) +{ + // This is technically not correct at all to do...however when overloading a root component the preedit gets called twice for some reason. + // Calling it twice attempts to double register it in the list and causes an assert to be thrown. + if (this->GetOwner()->IsA(AVRCharacter::StaticClass())) + return; + else + Super::PreEditChange(PropertyThatWillChange); +} + +void UVRRootComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None; + + // We only want to modify the property that was changed at this point + // things like propagation from CDO to instances don't work correctly if changing one property causes a different property to change + if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, CapsuleHalfHeight)) + { + CapsuleHalfHeight = FMath::Max3(0.f, CapsuleHalfHeight, CapsuleRadius); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, CapsuleRadius)) + { + CapsuleRadius = FMath::Clamp(CapsuleRadius, 0.f, CapsuleHalfHeight); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, VRCapsuleOffset)) + { + } + + if (!IsTemplate()) + { + //UpdateBodySetup(); // do this before reregistering components so that new values are used for collision + } + + return; + + // Overrode the defaults for this, don't call the parent + //Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif // WITH_EDITOR + + +// This overrides the movement logic to use the offset location instead of the default location for sweeps. +bool UVRRootComponent::MoveComponentImpl(const FVector& Delta, const FQuat& NewRotationQuat, bool bSweep, FHitResult* OutHit, EMoveComponentFlags MoveFlags, ETeleportType Teleport) +{ + + /*if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + return Super::MoveComponentImpl(Delta, NewRotationQuat, bSweep, OutHit, MoveFlags, Teleport); + GenerateOffsetToWorld(); + }*/ + + SCOPE_CYCLE_COUNTER(STAT_VRRootMovement); + //CSV_SCOPED_TIMING_STAT(PrimitiveComponent, MoveComponentTime); + + // static things can move before they are registered (e.g. immediately after streaming), but not after. + if (!IsValid(this) || (this->Mobility == EComponentMobility::Static && IsRegistered()))//|| CheckStaticMobilityAndWarn(PrimitiveComponentStatics::MobilityWarnText)) + { + if (OutHit) + { + OutHit->Init(); + } + return false; + } + + const bool bSkipPhysicsMove = ((MoveFlags & MOVECOMP_SkipPhysicsMove) != MOVECOMP_NoFlags); + + if (!this->IsSimulatingPhysics() && bSkipPhysicsMove) + { + // Phys thread is updating this when we don't want it to, stop it chaos! + return false; + } + + ConditionalUpdateComponentToWorld(); + + // Init HitResult + //FHitResult BlockingHit(1.f); + const FVector TraceStart = OffsetComponentToWorld.GetLocation();// .GetLocation();//GetComponentLocation(); + const FVector TraceEnd = TraceStart + Delta; + //BlockingHit.TraceStart = TraceStart; + //BlockingHit.TraceEnd = TraceEnd; + float DeltaSizeSq = (TraceEnd - TraceStart).SizeSquared(); // Recalc here to account for precision loss of float addition + + // Set up. +// float DeltaSizeSq = Delta.SizeSquared(); + const FQuat InitialRotationQuat = GetComponentTransform().GetRotation();//ComponentToWorld.GetRotation(); + + // ComponentSweepMulti does nothing if moving < UE_KINDA_SMALL_NUMBER in distance, so it's important to not try to sweep distances smaller than that. + const float MinMovementDistSq = (bSweep ? FMath::Square(4.f*UE_KINDA_SMALL_NUMBER) : 0.f); + if (DeltaSizeSq <= MinMovementDistSq) + { + // Skip if no vector or rotation. + if (NewRotationQuat.Equals(InitialRotationQuat, SCENECOMPONENT_QUAT_TOLERANCE)) + { + // copy to optional output param + if (OutHit) + { + OutHit->Init(TraceStart, TraceEnd); + } + return true; + } + DeltaSizeSq = 0.f; + } + + //const bool bSkipPhysicsMove = ((MoveFlags & MOVECOMP_SkipPhysicsMove) != MOVECOMP_NoFlags); + + // WARNING: HitResult is only partially initialized in some paths. All data is valid only if bFilledHitResult is true. + FHitResult BlockingHit(NoInit); + BlockingHit.bBlockingHit = false; + BlockingHit.Time = 1.f; + bool bFilledHitResult = false; + bool bMoved = false; + bool bIncludesOverlapsAtEnd = false; + bool bRotationOnly = false; + TInlineOverlapInfoArray PendingOverlaps; + AActor* const Actor = GetOwner(); + FVector OrigLocation = GetComponentLocation(); + + if (!bSweep) + { + // not sweeping, just go directly to the new transform + bMoved = InternalSetWorldLocationAndRotation(/*TraceEnd*/OrigLocation + Delta, NewRotationQuat, bSkipPhysicsMove, Teleport); + GenerateOffsetToWorld(); + bRotationOnly = (DeltaSizeSq == 0); + bIncludesOverlapsAtEnd = bRotationOnly && (AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale())) && IsCollisionEnabled(); + } + else + { + TArray Hits; + FVector NewLocation = OrigLocation;//TraceStart; + // Perform movement collision checking if needed for this actor. + const bool bCollisionEnabled = IsQueryCollisionEnabled(); + UWorld* const MyWorld = GetWorld(); + if (MyWorld && bCollisionEnabled && (DeltaSizeSq > 0.f)) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (!IsRegistered() && !MyWorld->bIsTearingDown) + { + if (Actor) + { + ensureMsgf(IsRegistered(), TEXT("%s MovedComponent %s not registered during sweep (IsValid %d)"), *Actor->GetName(), *GetName(), IsValid(Actor)); + } + else + { //-V523 + ensureMsgf(IsRegistered(), TEXT("Non-actor MovedComponent %s not registered during sweep"), *GetFullName()); + } + } +#endif +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) && PERF_MOVECOMPONENT_STATS + MoveTimer.bDidLineCheck = true; +#endif + static const FName TraceTagName = TEXT("MoveComponent"); + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + FComponentQueryParams Params(/*PrimitiveComponentStatics::MoveComponentName*//*"MoveComponent"*/SCENE_QUERY_STAT(MoveComponent), Actor); + FCollisionResponseParams ResponseParam; + InitSweepCollisionParams(Params, ResponseParam); + Params.bIgnoreTouches |= !(GetGenerateOverlapEvents() || bForceGatherOverlaps); + Params.TraceTag = TraceTagName; + bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params); + //bool const bHadBlockingHit = MyWorld->SweepMultiByChannel(Hits, TraceStart, TraceEnd, InitialRotationQuat, this->GetCollisionObjectType(), this->GetCollisionShape(), Params, ResponseParam); + + if (Hits.Num() > 0) + { + const float DeltaSize = FMath::Sqrt(DeltaSizeSq); + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + PullBackHit(Hits[HitIdx], TraceStart, TraceEnd, DeltaSize); + } + } + + // If we had a valid blocking hit, store it. + // If we are looking for overlaps, store those as well. + int32 FirstNonInitialOverlapIdx = INDEX_NONE; + if (bHadBlockingHit || (GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + int32 BlockingHitIndex = INDEX_NONE; + float BlockingHitNormalDotDelta = UE_BIG_NUMBER; + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + const FHitResult& TestHit = Hits[HitIdx]; + + if (TestHit.bBlockingHit) + { + if (!ShouldIgnoreHitResult(MyWorld, bAllowSimulatingCollision, TestHit, Delta, Actor, MoveFlags)) + { + if (TestHit.bStartPenetrating) + { + // We may have multiple initial hits, and want to choose the one with the normal most opposed to our movement. + const float NormalDotDelta = (TestHit.ImpactNormal | Delta); + if (NormalDotDelta < BlockingHitNormalDotDelta) + { + BlockingHitNormalDotDelta = NormalDotDelta; + BlockingHitIndex = HitIdx; + } + } + else if (BlockingHitIndex == INDEX_NONE) + { + // First non-overlapping blocking hit should be used, if an overlapping hit was not. + // This should be the only non-overlapping blocking hit, and last in the results. + BlockingHitIndex = HitIdx; + break; + } + } + } + else if (GetGenerateOverlapEvents() || bForceGatherOverlaps) + { + UPrimitiveComponent* OverlapComponent = TestHit.Component.Get(); + if (OverlapComponent && (OverlapComponent->GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + if (!ShouldIgnoreOverlapResult(MyWorld, Actor, *this, TestHit.HitObjectHandle.FetchActor(), *OverlapComponent,!bForceGatherOverlaps)) + { + // don't process touch events after initial blocking hits + if (BlockingHitIndex >= 0 && TestHit.Time > Hits[BlockingHitIndex].Time) + { + break; + } + + if (FirstNonInitialOverlapIdx == INDEX_NONE && TestHit.Time > 0.f) + { + // We are about to add the first non-initial overlap. + FirstNonInitialOverlapIdx = PendingOverlaps.Num(); + } + + // cache touches + AddUniqueOverlapFast(PendingOverlaps, FOverlapInfo(TestHit)); + } + } + } + } + + // Update blocking hit, if there was a valid one. + if (BlockingHitIndex >= 0) + { + BlockingHit = Hits[BlockingHitIndex]; + bFilledHitResult = true; + } + } + + // Update NewLocation based on the hit result + if (!BlockingHit.bBlockingHit) + { + NewLocation += (TraceEnd - TraceStart); + } + else + { + check(bFilledHitResult); + NewLocation += (BlockingHit.Time * (TraceEnd - TraceStart)); + + // Sanity check + const FVector ToNewLocation = (NewLocation - OrigLocation/*TraceStart*/); + if (ToNewLocation.SizeSquared() <= MinMovementDistSq) + { + // We don't want really small movements to put us on or inside a surface. + NewLocation = OrigLocation;//TraceStart; + BlockingHit.Time = 0.f; + + // Remove any pending overlaps after this point, we are not going as far as we swept. + if (FirstNonInitialOverlapIdx != INDEX_NONE) + { + const bool bAllowShrinking = false; + PendingOverlaps.SetNum(FirstNonInitialOverlapIdx, bAllowShrinking); + } + } + } + + bIncludesOverlapsAtEnd = AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale()); + } + else if (DeltaSizeSq > 0.f) + { + // apply move delta even if components has collisions disabled + NewLocation += Delta; + bIncludesOverlapsAtEnd = false; + } + else if (DeltaSizeSq == 0.f && bCollisionEnabled) + { + bIncludesOverlapsAtEnd = AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale()); + bRotationOnly = true; + } + + // Update the location. This will teleport any child components as well (not sweep). + bMoved = InternalSetWorldLocationAndRotation(NewLocation, NewRotationQuat, bSkipPhysicsMove, Teleport); + GenerateOffsetToWorld(); + } + + // Handle overlap notifications. + if (bMoved) + { + if (IsDeferringMovementUpdates()) + { + // Defer UpdateOverlaps until the scoped move ends. + FScopedMovementUpdate* ScopedUpdate = GetCurrentScopedMovement(); + if (bRotationOnly && bIncludesOverlapsAtEnd) + { + ScopedUpdate->KeepCurrentOverlapsAfterRotation(bSweep); + } + else + { + ScopedUpdate->AppendOverlapsAfterMove(PendingOverlaps, bSweep, bIncludesOverlapsAtEnd); + } + } + else + { + if (bIncludesOverlapsAtEnd) + { + TInlineOverlapInfoArray OverlapsAtEndLocation; + bool bHasEndOverlaps = false; + if (bRotationOnly) + { + // bHasEndOverlaps = ConvertRotationOverlapsToCurrentOverlaps(OverlapsAtEndLocation, OverlappingComponents); + } + else + { + // bHasEndOverlaps = ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEndLocation, PendingOverlaps, 0, OffsetComponentToWorld.GetLocation(), GetComponentQuat()); + } + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + TOverlapArrayView OverlapsAtEndView(OverlapsAtEndLocation); + UpdateOverlaps(&PendingOverlapsView, true, bHasEndOverlaps ? &OverlapsAtEndView : nullptr); + } + else + { + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + UpdateOverlaps(&PendingOverlapsView, true, nullptr); + } + } + } + + // Handle blocking hit notifications. Avoid if pending kill (which could happen after overlaps). + const bool bAllowHitDispatch = !BlockingHit.bStartPenetrating || !(MoveFlags & MOVECOMP_DisableBlockingOverlapDispatch); + if (BlockingHit.bBlockingHit && bAllowHitDispatch && IsValid(this)) + { + check(bFilledHitResult); + if (IsDeferringMovementUpdates()) + { + FScopedMovementUpdate* ScopedUpdate = GetCurrentScopedMovement(); + ScopedUpdate->AppendBlockingHitAfterMove(BlockingHit); + } + else + { + DispatchBlockingHit(*Actor, BlockingHit); + } + } + + // copy to optional output param + if (OutHit) + { + if (bFilledHitResult) + { + *OutHit = BlockingHit; + } + else + { + OutHit->Init(TraceStart, TraceEnd); + } + } + + // Return whether we moved at all. + return bMoved; +} + +bool UVRRootComponent::UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) +{ + /*if (owningVRChar && !owningVRChar->bRetainRoomscale) + { + return Super::UpdateOverlaps(NewPendingOverlaps, bDoNotifies, OverlapsAtEndLocation); + }*/ + + //SCOPE_CYCLE_COUNTER(STAT_UpdateOverlaps); + SCOPE_CYCLE_COUNTER(STAT_UpdateOverlapsVRRoot); + SCOPE_CYCLE_UOBJECT(ComponentScope, this); + + // if we haven't begun play, we're still setting things up (e.g. we might be inside one of the construction scripts) + // so we don't want to generate overlaps yet. There is no need to update children yet either, they will update once we are allowed to as well. + const AActor* const MyActor = GetOwner(); + if (MyActor && !MyActor->HasActorBegunPlay() && !MyActor->IsActorBeginningPlay()) + { + return false; + } + + bool bCanSkipUpdateOverlaps = true; + + // first, dispatch any pending overlaps + if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled()) //TODO: should modifying query collision remove from mayoverlapevents? + { + bCanSkipUpdateOverlaps = false; + + if (MyActor) + { + const FTransform PrevTransform = GetComponentTransform(); + // If we are the root component we ignore child components. Those children will update their overlaps when we descend into the child tree. + // This aids an optimization in MoveComponent. + const bool bIgnoreChildren = (MyActor->GetRootComponent() == this); + + if (NewPendingOverlaps) + { + // Note: BeginComponentOverlap() only triggers overlaps where GetGenerateOverlapEvents() is true on both components. + const int32 NumNewPendingOverlaps = NewPendingOverlaps->Num(); + for (int32 Idx = 0; Idx < NumNewPendingOverlaps; ++Idx) + { + BeginComponentOverlap((*NewPendingOverlaps)[Idx], bDoNotifies); + } + } + + const TOverlapArrayView* OverlapsAtEndLocationPtr = OverlapsAtEndLocation; + + // #TODO: Filter this better so it runs even less often? + // Its not that bad currently running off of NewPendingOverlaps + // It forces checking for end location overlaps again if none are registered, just in case + // the capsule isn't setting things correctly. + + TArray OverlapsAtEnd; + TOverlapArrayView OverlapsAtEndLoc; + if (/*(!OverlapsAtEndLocation || OverlapsAtEndLocation->Num() < 1) &&*/ NewPendingOverlaps && NewPendingOverlaps->Num() > 0) + { + ConvertSweptOverlapsToCurrentOverlapsVR(OverlapsAtEnd, *NewPendingOverlaps, -1, OffsetComponentToWorld.GetLocation(), GetComponentQuat()); + OverlapsAtEndLoc = TOverlapArrayView(OverlapsAtEnd); + OverlapsAtEndLocationPtr = &OverlapsAtEndLoc; + } + + // now generate full list of new touches, so we can compare to existing list and determine what changed + + TInlineOverlapInfoArray OverlapMultiResult; + TInlineOverlapPointerArray NewOverlappingComponentPtrs; + + // If pending kill, we should not generate any new overlaps. Also not if overlaps were just disabled during BeginComponentOverlap. + if (IsValid(this) && GetGenerateOverlapEvents()) + { + // 4.17 converted to auto cvar + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + // Might be able to avoid testing for new overlaps at the end location. + if (OverlapsAtEndLocationPtr != nullptr && CVarAllowCachedOverlaps->GetInt() > 0 && PrevTransform.Equals(GetComponentTransform())) + { + UE_LOG(LogVRRootComponent, VeryVerbose, TEXT("%s->%s Skipping overlap test!"), *GetNameSafe(GetOwner()), *GetName()); + const bool bCheckForInvalid = (NewPendingOverlaps && NewPendingOverlaps->Num() > 0); + if (bCheckForInvalid) + { + // BeginComponentOverlap may have disabled what we thought were valid overlaps at the end (collision response or overlap flags could change). + GetPointersToArrayDataByPredicate(NewOverlappingComponentPtrs, *OverlapsAtEndLocationPtr, FPredicateFilterCanOverlap(*this)); + } + else + { + GetPointersToArrayData(NewOverlappingComponentPtrs, *OverlapsAtEndLocationPtr); + } + } + else + { + SCOPE_CYCLE_COUNTER(STAT_PerformOverlapQueryVR); + UE_LOG(LogVRRootComponent, VeryVerbose, TEXT("%s->%s Performing overlaps!"), *GetNameSafe(GetOwner()), *GetName()); + UWorld* const MyWorld = GetWorld(); + TArray Overlaps; + // note this will optionally include overlaps with components in the same actor (depending on bIgnoreChildren). + + FComponentQueryParams Params(SCENE_QUERY_STAT(UpdateOverlaps), bIgnoreChildren ? MyActor : nullptr); //(PrimitiveComponentStatics::UpdateOverlapsName, bIgnoreChildren ? MyActor : nullptr); + + Params.bIgnoreBlocks = true; //We don't care about blockers since we only route overlap events to real overlaps + FCollisionResponseParams ResponseParam; + InitSweepCollisionParams(Params, ResponseParam); + ComponentOverlapMulti(Overlaps, MyWorld, OffsetComponentToWorld.GetTranslation(), GetComponentQuat(), GetCollisionObjectType(), Params); + + for (int32 ResultIdx = 0; ResultIdx < Overlaps.Num(); ResultIdx++) + { + const FOverlapResult& Result = Overlaps[ResultIdx]; + + UPrimitiveComponent* const HitComp = Result.Component.Get(); + if (HitComp && (HitComp != this) && HitComp->GetGenerateOverlapEvents()) + { + const bool bCheckOverlapFlags = false; // Already checked above + if (!ShouldIgnoreOverlapResult(MyWorld, MyActor, *this, Result.OverlapObjectHandle.FetchActor(), *HitComp, bCheckOverlapFlags)) + { + OverlapMultiResult.Emplace(HitComp, Result.ItemIndex); // don't need to add unique unless the overlap check can return dupes + } + } + } + + // Fill pointers to overlap results. We ensure below that OverlapMultiResult stays in scope so these pointers remain valid. + GetPointersToArrayData(NewOverlappingComponentPtrs, OverlapMultiResult); + } + } + + // If we have any overlaps from BeginComponentOverlap() (from now or in the past), see if anything has changed by filtering NewOverlappingComponents + if (OverlappingComponents.Num() > 0) + { + TInlineOverlapPointerArray OldOverlappingComponentPtrs; + if (bIgnoreChildren) + { + GetPointersToArrayDataByPredicate(OldOverlappingComponentPtrs, OverlappingComponents, FPredicateOverlapHasDifferentActor(*MyActor)); + } + else + { + GetPointersToArrayData(OldOverlappingComponentPtrs, OverlappingComponents); + } + + // Now we want to compare the old and new overlap lists to determine + // what overlaps are in old and not in new (need end overlap notifies), and + // what overlaps are in new and not in old (need begin overlap notifies). + // We do this by removing common entries from both lists, since overlapping status has not changed for them. + // What is left over will be what has changed. + for (int32 CompIdx = 0; CompIdx < OldOverlappingComponentPtrs.Num() && NewOverlappingComponentPtrs.Num() > 0; ++CompIdx) + { + // RemoveAtSwap is ok, since it is not necessary to maintain order + const bool bAllowShrinking = false; + + const FOverlapInfo* SearchItem = OldOverlappingComponentPtrs[CompIdx]; + const int32 NewElementIdx = IndexOfOverlapFast(NewOverlappingComponentPtrs, SearchItem); + if (NewElementIdx != INDEX_NONE) + { + NewOverlappingComponentPtrs.RemoveAtSwap(NewElementIdx, 1, bAllowShrinking); + OldOverlappingComponentPtrs.RemoveAtSwap(CompIdx, 1, bAllowShrinking); + --CompIdx; + } + } + + const int32 NumOldOverlaps = OldOverlappingComponentPtrs.Num(); + if (NumOldOverlaps > 0) + { + // Now we have to make a copy of the overlaps because we can't keep pointers to them, that list is about to be manipulated in EndComponentOverlap(). + TInlineOverlapInfoArray OldOverlappingComponents; + OldOverlappingComponents.SetNumUninitialized(NumOldOverlaps); + for (int32 i = 0; i < NumOldOverlaps; i++) + { + OldOverlappingComponents[i] = *(OldOverlappingComponentPtrs[i]); + } + + // OldOverlappingComponents now contains only previous overlaps that are confirmed to no longer be valid. + for (const FOverlapInfo& OtherOverlap : OldOverlappingComponents) + { + if (OtherOverlap.OverlapInfo.Component.IsValid()) + { + EndComponentOverlap(OtherOverlap, bDoNotifies, false); + } + else + { + // Remove stale item. Reclaim memory only if it's getting large, to try to avoid churn but avoid bloating component's memory usage. + const bool bAllowShrinking = (OverlappingComponents.Max() >= 24); + const int32 StaleElementIndex = IndexOfOverlapFast(OverlappingComponents, OtherOverlap); + if (StaleElementIndex != INDEX_NONE) + { + OverlappingComponents.RemoveAtSwap(StaleElementIndex, 1, bAllowShrinking); + } + } + } + } + } + + // Ensure these arrays are still in scope, because we kept pointers to them in NewOverlappingComponentPtrs. + static_assert(sizeof(OverlapMultiResult) != 0, "Variable must be in this scope"); + static_assert(sizeof(*OverlapsAtEndLocation) != 0, "Variable must be in this scope"); + + // NewOverlappingComponents now contains only new overlaps that didn't exist previously. + for (const FOverlapInfo* NewOverlap : NewOverlappingComponentPtrs) + { + BeginComponentOverlap(*NewOverlap, bDoNotifies); + } + } + } + else + { + // GetGenerateOverlapEvents() is false or collision is disabled + // End all overlaps that exist, in case GetGenerateOverlapEvents() was true last tick (i.e. was just turned off) + if (OverlappingComponents.Num() > 0) + { + const bool bSkipNotifySelf = false; + ClearComponentOverlaps(bDoNotifies, bSkipNotifySelf); + } + } + + // now update any children down the chain. + // since on overlap events could manipulate the child array we need to take a copy + // of it to avoid missing any children if one is removed from the middle + TInlineComponentArray AttachedChildren; + AttachedChildren.Append(GetAttachChildren()); + + for (USceneComponent* const ChildComp : AttachedChildren) + { + if (ChildComp) + { + // Do not pass on OverlapsAtEndLocation, it only applied to this component. + bCanSkipUpdateOverlaps &= ChildComp->UpdateOverlaps(nullptr, bDoNotifies, nullptr); + } + } + + // Update physics volume using most current overlaps + if (GetShouldUpdatePhysicsVolume()) + { + UpdatePhysicsVolume(bDoNotifies); + bCanSkipUpdateOverlaps = false; + } + + return bCanSkipUpdateOverlaps; +} + +bool UVRRootComponent::IsLocallyControlled() const +{ + // I like epics implementation better than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner->Role == ENetRole::ROLE_Authority); +} + +/*FORCEINLINE*/ void UVRRootComponent::SetCapsuleSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + SCOPE_CYCLE_COUNTER(STAT_VRRootSetCapsuleSize); + + FScopedMovementUpdate ScopedNetSmootherMovementUpdate(owningVRChar ? owningVRChar->NetSmoother : nullptr, EScopedUpdate::DeferredUpdates); + + if (FMath::IsNearlyEqual(NewRadius, CapsuleRadius) && FMath::IsNearlyEqual(NewHalfHeight, CapsuleHalfHeight)) + { + return; + } + + float OldCapsuleHalfHeight = CapsuleHalfHeight; + + CapsuleHalfHeight = FMath::Max3(0.f, NewHalfHeight, NewRadius); + CapsuleRadius = FMath::Max(0.f, NewRadius); + + UpdateBounds(); + UpdateBodySetup(); + MarkRenderStateDirty(); + GenerateOffsetToWorld(); + + // do this if already created + // otherwise, it hasn't been really created yet + if (bPhysicsStateCreated) + { + // Update physics engine collision shapes + BodyInstance.UpdateBodyScale(GetComponentTransform().GetScale3D(), true); + + if (bUpdateOverlaps && IsCollisionEnabled() && GetOwner()) + { + UpdateOverlaps(); + } + } + + // Make sure that our character parent updates its replicated var as well + if (owningVRChar) + { + if (GetNetMode() < ENetMode::NM_Client) + { + if (owningVRChar->VRReplicateCapsuleHeight) + { + owningVRChar->ReplicatedCapsuleHeight.CapsuleHeight = CapsuleHalfHeight; + } + } + + float Offset = (CapsuleHalfHeight - OldCapsuleHalfHeight); + + // Simulated proxies should already have the new height from the server + if (!owningVRChar->bRetainRoomscale && (owningVRChar->GetNetMode() < ENetMode::NM_Client || IsLocallyControlled())) + { + MoveComponent(this->GetComponentQuat().GetUpVector() * (Offset * this->GetComponentScale().Z), GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + } + /*else + { + // Generate final transform + GenerateOffsetToWorld(); + }*/ + + if (!owningVRChar->bRetainRoomscale && !IsLocallyControlled()) + { + // Don't smooth this change in mesh position + FNetworkPredictionData_Client_Character* ClientData = owningVRChar->GetCharacterMovement()->GetPredictionData_Client_Character(); + if (ClientData) + { + ClientData->MeshTranslationOffset.Z += (Offset * this->GetComponentScale().Z);// FVector::ZeroVector;// -= FVector(0.f, 0.f, Offset * this->GetComponentScale().Z); + ClientData->OriginalMeshTranslationOffset.Z = ClientData->MeshTranslationOffset.Z; + } + } + } + /*else + { + // Generate final transform + GenerateOffsetToWorld(); + }*/ +} + +void UVRRootComponent::UpdatePhysicsVolume(bool bTriggerNotifiers) +{ + if (GetShouldUpdatePhysicsVolume() && IsValid(this)) + { + // SCOPE_CYCLE_COUNTER(STAT_UpdatePhysicsVolume); + if (UWorld * MyWorld = GetWorld()) + { + if (MyWorld->GetNonDefaultPhysicsVolumeCount() == 0) + { + SetPhysicsVolume(MyWorld->GetDefaultPhysicsVolume(), bTriggerNotifiers); + } + else if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled()) + { + APhysicsVolume* BestVolume = MyWorld->GetDefaultPhysicsVolume(); + int32 BestPriority = BestVolume->Priority; + + for (auto CompIt = OverlappingComponents.CreateIterator(); CompIt; ++CompIt) + { + const FOverlapInfo& Overlap = *CompIt; + UPrimitiveComponent* OtherComponent = Overlap.OverlapInfo.Component.Get(); + if (OtherComponent && OtherComponent->GetGenerateOverlapEvents()) + { + APhysicsVolume* V = Cast(OtherComponent->GetOwner()); + if (V && V->Priority > BestPriority) + { + //if (V->IsOverlapInVolume(*this)) + if (AreWeOverlappingVolume(V)) + { + BestPriority = V->Priority; + BestVolume = V; + } + } + } + } + + SetPhysicsVolume(BestVolume, bTriggerNotifiers); + } + else + { + Super::UpdatePhysicsVolume(bTriggerNotifiers); + } + } + } +} + +template +bool UVRRootComponent::ConvertSweptOverlapsToCurrentOverlapsVR( + TArray& OverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, + const FVector& EndLocation, const FQuat& EndRotationQuat) +{ + if (SweptOverlapsIndex == -1) + { + SweptOverlapsIndex = 0; + } + else + { + return false; + } + + checkSlow(SweptOverlapsIndex >= 0); + + // Override location check with our own + //GenerateOffsetToWorld(); + FVector EndLocationVR = OffsetComponentToWorld.GetLocation(); + + + bool bResult = false; + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && CVarAllowCachedOverlaps->GetInt()) + { + const AActor* Actor = GetOwner(); + if (Actor && Actor->GetRootComponent() == this) + { + + static const auto CVarEnableFastOverlapCheck = IConsoleManager::Get().FindConsoleVariable(TEXT("p.EnableFastOverlapCheck")); + // We know we are not overlapping any new components at the end location. Children are ignored here (see note below). + if (CVarAllowCachedOverlaps->GetInt()) + { + //SCOPE_CYCLE_COUNTER(STAT_MoveComponent_FastOverlap); + + // Check components we hit during the sweep, keep only those still overlapping + const FCollisionQueryParams UnusedQueryParams(NAME_None, FCollisionQueryParams::GetUnknownStatId()); + const int32 NumSweptOverlaps = SweptOverlaps.Num(); + OverlapsAtEndLocation.Reserve(OverlapsAtEndLocation.Num() + NumSweptOverlaps); + for (int32 Index = SweptOverlapsIndex; Index < NumSweptOverlaps; ++Index) + { + const FOverlapInfo& OtherOverlap = SweptOverlaps[Index]; + UPrimitiveComponent* OtherPrimitive = OtherOverlap.OverlapInfo.GetComponent(); + if (OtherPrimitive && (OtherPrimitive->GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + if (OtherPrimitive->bMultiBodyOverlap) + { + // Not handled yet. We could do it by checking every body explicitly and track each body index in the overlap test, but this seems like a rare need. + return false; + } + else if (Cast(OtherPrimitive) || Cast(this)) + { + // SkeletalMeshComponent does not support this operation, and would return false in the test when an actual query could return true. + return false; + } + else if (OtherPrimitive->ComponentOverlapComponent(this, EndLocationVR, EndRotationQuat, UnusedQueryParams)) + { + OverlapsAtEndLocation.Add(OtherOverlap); + } + } + } + + // Note: we don't worry about adding any child components here, because they are not included in the sweep results. + // Children test for their own overlaps after we update our own, and we ignore children in our own update. + checkfSlow(OverlapsAtEndLocation.FindByPredicate(FPredicateOverlapHasSameActor(*Actor)) == nullptr, + TEXT("Child overlaps should not be included in the SweptOverlaps() array in UPrimitiveComponent::ConvertSweptOverlapsToCurrentOverlaps().")); + + bResult = true; + } + else + { + if (SweptOverlaps.Num() == 0 && AreAllCollideableDescendantsRelative()) + { + // Add overlaps with components in this actor. + GetOverlapsWithActor_TemplateVR(Actor, OverlapsAtEndLocation); + bResult = true; + } + } + } + } + + return bResult; +} + +template +bool UVRRootComponent::ConvertRotationOverlapsToCurrentOverlapsVR(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps) +{ + bool bResult = false; + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + + if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && /*bAllowCachedOverlapsCVar*/ CVarAllowCachedOverlaps->GetInt()) + { + const AActor* Actor = GetOwner(); + if (Actor && Actor->GetRootComponent() == this) + { + static const auto CVarEnableFastOverlapCheck = IConsoleManager::Get().FindConsoleVariable(TEXT("p.EnableFastOverlapCheck")); + // We know we are not overlapping any new components at the end location. Children are ignored here (see note below). + if (CVarAllowCachedOverlaps->GetInt()) + { + // Add all current overlaps that are not children. Children test for their own overlaps after we update our own, and we ignore children in our own update. + OutOverlapsAtEndLocation.Reserve(OutOverlapsAtEndLocation.Num() + CurrentOverlaps.Num()); + Algo::CopyIf(CurrentOverlaps, OutOverlapsAtEndLocation, FPredicateOverlapHasDifferentActor(*Actor)); + bResult = true; + } + } + } + + return bResult; +} + +template +bool UVRRootComponent::GetOverlapsWithActor_TemplateVR(const AActor* Actor, TArray& OutOverlaps) const +{ + const int32 InitialCount = OutOverlaps.Num(); + if (Actor) + { + for (int32 OverlapIdx = 0; OverlapIdx < OverlappingComponents.Num(); ++OverlapIdx) + { + UPrimitiveComponent const* const PrimComp = OverlappingComponents[OverlapIdx].OverlapInfo.Component.Get(); + if (PrimComp && (PrimComp->GetOwner() == Actor)) + { + OutOverlaps.Add(OverlappingComponents[OverlapIdx]); + } + } + } + + return InitialCount != OutOverlaps.Num(); +} +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp new file mode 100644 index 0000000..a6dd7bb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp @@ -0,0 +1,1258 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRStereoWidgetComponent.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRStereoWidgetComponent) + +#include "VRExpansionFunctionLibrary.h" +#include "IXRTrackingSystem.h" +#include "IXRCamera.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "VRRootComponent.h" +#include "TextureResource.h" +#include "Engine/Texture.h" +#include "Engine/GameInstance.h" +#include "Materials/Material.h" +#include "IStereoLayers.h" +#include "IHeadMountedDisplay.h" +#include "PrimitiveViewRelevance.h" +#include "PrimitiveSceneProxy.h" +#include "UObject/ConstructorHelpers.h" +#include "EngineGlobals.h" +#include "MaterialShared.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Materials/MaterialRenderProxy.h" +#include "Engine/Engine.h" +#include "Engine/GameViewportClient.h" +#include "Engine/TextureRenderTarget2D.h" +//#include "Widgets/SWindow.h" +#include "Framework/Application/SlateApplication.h" +#include "Kismet/KismetSystemLibrary.h" +//#include "Input/HittestGrid.h" +//#include "SceneManagement.h" +#include "DynamicMeshBuilder.h" +//#include "PhysicsEngine/BoxElem.h" +#include "PhysicsEngine/BodySetup.h" +#include "Slate/SGameLayerManager.h" +#include "Slate/SWorldWidgetScreenLayer.h" + +#include "Widgets/SViewport.h" +#include "Widgets/SViewport.h" +#include "Slate/WidgetRenderer.h" +#include "Blueprint/UserWidget.h" +#include "SceneInterface.h" + +#include "StereoLayerShapes.h" + +// CVars +namespace StereoWidgetCvars +{ + static int32 ForceNoStereoWithVRWidgets = 0; + FAutoConsoleVariableRef CVarForceNoStereoWithVRWidgets( + TEXT("vr.ForceNoStereoWithVRWidgets"), + ForceNoStereoWithVRWidgets, + TEXT("When set to 0, will render stereo layer widgets as stereo by default.\n") + TEXT("When set to 1, will not allow stereo widget components to use stereo layers, will instead fall back to default widget rendering.\n") + TEXT("When set to 2, will render stereo layer widgets as both stereo and in game.\n") + TEXT("0: Default, 1: Force no stereo, 2: Render both at once"), + ECVF_Default); +} + +UVRStereoWidgetRenderComponent::UVRStereoWidgetRenderComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + Widget = nullptr; + WidgetRenderScale = 1.0f; + WidgetRenderGamma = 1.0f; + bUseGammaCorrection = false; + WidgetRenderer = nullptr; + RenderTarget = nullptr; + bDrawAtDesiredSize = true; + RenderTargetClearColor = FLinearColor::Black; + bDrawWithoutStereo = false; + DrawRate = 60.0f; + DrawCounter = 0.0f; + bLiveTexture = true; +} + +void UVRStereoWidgetRenderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + + IStereoLayers* StereoLayers; + if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr))) + { + } + else + { + DrawCounter += DeltaTime; + + if (DrawRate > 0.0f && DrawCounter >= (1.0f / DrawRate)) + { + if (!IsRunningDedicatedServer()) + { + RenderWidget(DeltaTime); + } + + if (!bLiveTexture) + { + MarkStereoLayerDirty(); + } + + DrawCounter = 0.0f; + } + } + + if (bDrawWithoutStereo) + { + // Skip the stereo comps setup, we are just drawing to the texture + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } + else + { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } +} + +void UVRStereoWidgetRenderComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (WidgetClass.Get() != nullptr) + { + InitWidget(); + + IStereoLayers* StereoLayers; + if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr))) + { + } + else + { + // Initial render + RenderWidget(0.0f); + } + } +} + +void UVRStereoWidgetRenderComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + ReleaseResources(); +} + +void UVRStereoWidgetRenderComponent::ReleaseResources() +{ + +#if !UE_SERVER + FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); +#endif + + if (Widget) + { + Widget = nullptr; + } + + if (SlateWidget) + { + SlateWidget = nullptr; + } + + if (WidgetRenderer) + { + BeginCleanup(WidgetRenderer); + WidgetRenderer = nullptr; + } + + Texture = nullptr; + + if (SlateWindow.IsValid()) + { + if (/*!CanReceiveHardwareInput() && */FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().UnregisterVirtualWindow(SlateWindow.ToSharedRef()); + } + + SlateWindow.Reset(); + } +} + +void UVRStereoWidgetRenderComponent::DestroyComponent(bool bPromoteChildren/*= false*/) +{ + Super::DestroyComponent(bPromoteChildren); + + ReleaseResources(); +} + +void UVRStereoWidgetRenderComponent::SetWidgetAndInit(TSubclassOf NewWidgetClass) +{ + WidgetClass = NewWidgetClass; + InitWidget(); +} + +void UVRStereoWidgetRenderComponent::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) +{ + // If the InLevel is null, it's a signal that the entire world is about to disappear, so + // go ahead and remove this widget from the viewport, it could be holding onto too many + // dangerous actor references that won't carry over into the next world. + if (InLevel == nullptr && InWorld == GetWorld()) + { + ReleaseResources(); + } +} + +void UVRStereoWidgetRenderComponent::InitWidget() +{ + if (IsTemplate()) + return; + +#if !UE_SERVER + FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &ThisClass::OnLevelRemovedFromWorld); +#endif + if (IsRunningDedicatedServer()) + return; + + if (Widget && Widget->GetClass() == WidgetClass) + return; + + if (Widget != nullptr) + { + Widget->MarkAsGarbage(); + Widget = nullptr; + } + + if (SlateWidget) + { + SlateWidget = nullptr; + } + + // Don't do any work if Slate is not initialized + if (FSlateApplication::IsInitialized()) + { + UWorld* World = GetWorld(); + + if (WidgetClass && Widget == nullptr && World && !World->bIsTearingDown) + { + Widget = CreateWidget(GetWorld(), WidgetClass); + Widget->SetRenderScale(FVector2D(1.0f, 1.0f)); + } + +#if WITH_EDITOR + if (Widget && !World->IsGameWorld())// && !bEditTimeUsable) + { + if (!GEnableVREditorHacks) + { + // Prevent native ticking of editor component previews + Widget->SetDesignerFlags(EWidgetDesignFlags::Designing); + } + } +#endif + + if (Widget) + { + SlateWidget = Widget->TakeWidget(); + } + + // Create the SlateWindow if it doesn't exists + if (!SlateWindow.IsValid()) + { + FVector2D DrawSize = this->GetQuadSize(); + SlateWindow = SNew(SVirtualWindow).Size(DrawSize); + SlateWindow->SetIsFocusable(false); + SlateWindow->SetVisibility(EVisibility::Visible); + SlateWindow->SetContentScale(FVector2D(1.0f, 1.0f)); + + if (Widget && !Widget->IsDesignTime()) + { + if (UWorld* LocalWorld = GetWorld()) + { + if (LocalWorld->IsGameWorld()) + { + UGameInstance* GameInstance = LocalWorld->GetGameInstance(); + check(GameInstance); + + UGameViewportClient* GameViewportClient = GameInstance->GetGameViewportClient(); + if (GameViewportClient) + { + SlateWindow->AssignParentWidget(GameViewportClient->GetGameViewportWidget()); + } + } + } + } + + } + + if (SlateWindow) + { + TSharedRef MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget(); + SlateWindow->SetContent(MyWidget); + } + } +} + +void UVRStereoWidgetRenderComponent::RenderWidget(float DeltaTime) +{ + if (!Widget) + return; + + if (WidgetRenderer == nullptr) + { + WidgetRenderer = new FWidgetRenderer(bUseGammaCorrection); + check(WidgetRenderer); + } + + FVector2D DrawSize = this->GetQuadSize(); + FVector2D TextureSize = DrawSize; + + const int32 MaxAllowedDrawSize = GetMax2DTextureDimension(); + if (DrawSize.X <= 0 || DrawSize.Y <= 0 || DrawSize.X > MaxAllowedDrawSize || DrawSize.Y > MaxAllowedDrawSize) + { + return; + } + + TSharedRef MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget(); + + if (bDrawAtDesiredSize) + { + SlateWindow->SlatePrepass(WidgetRenderScale); + + FVector2D DesiredSize = SlateWindow->GetDesiredSize(); + DesiredSize.X = FMath::RoundToInt(DesiredSize.X); + DesiredSize.Y = FMath::RoundToInt(DesiredSize.Y); + + if (!DesiredSize.IsNearlyZero()) + { + TextureSize = DesiredSize;// .IntPoint(); + + WidgetRenderer->SetIsPrepassNeeded(false); + + if (SlateWindow->GetSizeInScreen() != DesiredSize) + { + SlateWindow->Resize(TextureSize); + } + } + else + { + WidgetRenderer->SetIsPrepassNeeded(true); + } + } + else + { + WidgetRenderer->SetIsPrepassNeeded(true); + } + + if (RenderTarget == nullptr) + { + const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat(); + RenderTarget = NewObject(); + check(RenderTarget); + RenderTarget->AddToRoot(); + RenderTarget->ClearColor = RenderTargetClearColor; + RenderTarget->TargetGamma = WidgetRenderGamma; + RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false); + MarkStereoLayerDirty(); + } + else if (RenderTarget->GetResource()->GetSizeX() != TextureSize.X || RenderTarget->GetResource()->GetSizeY() != TextureSize.Y) + { + const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat(); + RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false); + RenderTarget->UpdateResourceImmediate(); + MarkStereoLayerDirty(); + } + + WidgetRenderer->DrawWidget(RenderTarget, MyWidget, WidgetRenderScale, TextureSize, DeltaTime);//DeltaTime); + + if (Texture != RenderTarget) + { + Texture = RenderTarget; + } +} + + //============================================================================= +UVRStereoWidgetComponent::UVRStereoWidgetComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +// , bLiveTexture(false) + , bSupportsDepth(false) + , bNoAlphaChannel(false) + //, Texture(nullptr) + //, LeftTexture(nullptr) + , bQuadPreserveTextureRatio(false) + //, StereoLayerQuadSize(FVector2D(500.0f, 500.0f)) + , UVRect(FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f))) + //, CylinderRadius(100) + //, CylinderOverlayArc(100)EWidgetGeometryMode + //, CylinderHeight(50) + //, StereoLayerType(SLT_TrackerLocked) + //, StereoLayerShape(SLSH_QuadLayer) + , Priority(0) + , bIsDirty(true) + , bTextureNeedsUpdate(false) + , LayerId(IStereoLayers::FLayerDesc::INVALID_LAYER_ID) + , LastTransform(FTransform::Identity) + , bLastVisible(false) +{ + bShouldCreateProxy = true; + bUseEpicsWorldLockedStereo = false; + // Replace quad size with DrawSize instead + //StereoLayerQuadSize = DrawSize; + + PrimaryComponentTick.TickGroup = TG_DuringPhysics; + + bIsDirty = true; + bDirtyRenderTarget = false; + bRenderBothStereoAndWorld = false; + bDrawWithoutStereo = false; + bDelayForRenderThread = false; + bIsSleeping = false; + //Texture = nullptr; +} + +//============================================================================= +UVRStereoWidgetComponent::~UVRStereoWidgetComponent() +{ +} + +void UVRStereoWidgetComponent::BeginDestroy() +{ + IStereoLayers* StereoLayers; + if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID; + } + + Super::BeginDestroy(); +} + + +void UVRStereoWidgetComponent::OnUnregister() +{ + IStereoLayers* StereoLayers; + if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID; + } + + Super::OnUnregister(); +} + +void UVRStereoWidgetComponent::DrawWidgetToRenderTarget(float DeltaTime) +{ + Super::DrawWidgetToRenderTarget(DeltaTime); + + bDirtyRenderTarget = true; +} + +void UVRStereoWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + // Precaching what the widget uses for draw time here as it gets modified in the super tick + bool bWidgetDrew = ShouldDrawWidget(); + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + if (IsRunningDedicatedServer()) + { + SetTickMode(ETickMode::Disabled); + return; + } + + //bool bIsCurVis = IsWidgetVisible(); + + bool bIsVisible = (bAlwaysVisible && !bIsSleeping) || (IsVisible() && IsWidgetVisible() && !bIsSleeping);// && ((GetWorld()->TimeSince(GetLastRenderTime()) <= 0.5f)); + + // If we are set to not use stereo layers or we don't have a valid stereo layer device + if ( + StereoWidgetCvars::ForceNoStereoWithVRWidgets == 1 || + bDrawWithoutStereo || + !UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() || + !GEngine->StereoRenderingDevice.IsValid() || + (GEngine->StereoRenderingDevice->GetStereoLayers() == nullptr) + ) + { + if (!bShouldCreateProxy) + { + bShouldCreateProxy = true; + //MarkRenderStateDirty(); // Recreate + + if (LayerId) + { + if (GEngine->StereoRenderingDevice.IsValid()) + { + IStereoLayers* StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers(); + if (StereoLayers) + StereoLayers->DestroyLayer(LayerId); + } + LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID; + } + } + + return; + } + else if (bRenderBothStereoAndWorld || StereoWidgetCvars::ForceNoStereoWithVRWidgets == 2) // Forcing both modes at once + { + if (!bShouldCreateProxy) + { + bShouldCreateProxy = true; + MarkRenderStateDirty(); // Recreate + } + } + else // Stereo only + { + if (bShouldCreateProxy) + { + bShouldCreateProxy = false; + MarkRenderStateDirty(); // Recreate + } + } + +#if !UE_SERVER + + // Same check that the widget runs prior to ticking + if (IsRunningDedicatedServer() || !GetSlateWindow() || GetSlateWindow()->GetContent() == SNullWidget::NullWidget) + { + return; + } + + IStereoLayers* StereoLayers; + if (!UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() || !GEngine->StereoRenderingDevice.IsValid() || !RenderTarget) + { + return; + } + + StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers(); + + if (StereoLayers == nullptr) + return; + + FTransform Transform = LastTransform; + // Never true until epic fixes back end code + if (false)//StereoLayerType == SLT_WorldLocked) + { + Transform = GetComponentTransform(); + } + else if (Space == EWidgetSpace::Screen) + { + Transform = GetRelativeTransform(); + } + else if(bIsVisible) // World locked here now + { + + if (bUseEpicsWorldLockedStereo) + { + // Its incorrect......even in 4.17 + Transform = FTransform(FRotator(0.f,-180.f, 0.f)) * GetComponentTransform(); + //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion()); + } + else + { + // Fix this when stereo world locked works again + // Thanks to mitch for the temp work around idea + + APlayerController* PC = nullptr; + if (UWorld * CurWorld = GetWorld()) + { + const ULocalPlayer* FirstPlayer = GEngine->GetFirstGamePlayer(CurWorld); + PC = FirstPlayer ? FirstPlayer->GetPlayerController(CurWorld) : nullptr; + } + + if (PC) + { + APawn * mpawn = PC->GetPawnOrSpectator(); + //bTextureNeedsUpdate = true; + if (mpawn) + { + + // Offset the transform by the widget pivot. + float DeltaY = (Pivot.X - 0.5f) * DrawSize.X; + float DeltaZ = (Pivot.Y - 0.5f) * DrawSize.Y; + FTransform OffsetTransform = FTransform(FVector(0.f, DeltaY, DeltaZ)); + OffsetTransform = OffsetTransform * GetComponentTransform(); + + // Set transform to this relative transform + + bool bHandledTransform = false; + if (AVRBaseCharacter* BaseVRChar = Cast(mpawn)) + { + if (USceneComponent* CameraParent = BaseVRChar->VRReplicatedCamera->GetAttachParent()) + { + FTransform DeltaTrans = FTransform::Identity; + if (!BaseVRChar->bRetainRoomscale) + { + FVector HMDLoc; + FQuat HMDRot; + GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HMDRot, HMDLoc); + + HMDLoc.Z = 0.0f; + + if (AVRCharacter* VRChar = Cast(mpawn)) + { + HMDLoc += UVRExpansionFunctionLibrary::GetHMDPureYaw_I(HMDRot.Rotator()).RotateVector(FVector(VRChar->VRRootReference->VRCapsuleOffset.X, VRChar->VRRootReference->VRCapsuleOffset.Y, 0.0f)); + } + + DeltaTrans = FTransform(FQuat::Identity, HMDLoc, FVector(1.0f)); + } + + Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform()); + Transform = (FTransform(FRotator(0.f, -180.f, 0.f)) * Transform) * DeltaTrans; + bHandledTransform = true; + } + } + else if (UCameraComponent* Camera = mpawn->FindComponentByClass()) + { + // Look for generic camera comp and use its attach parent + if (USceneComponent* CameraParent = Camera->GetAttachParent()) + { + Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform()); + Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform; + bHandledTransform = true; + } + } + + if(!bHandledTransform) // Just use the pawn as we don't know the heirarchy + { + Transform = OffsetTransform.GetRelativeTransform(mpawn->GetTransform()); + Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform; + } + + // OpenVR y+ Up, +x Right, -z Going away + // UE4 z+ up, +y right, +x forward + + //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion()); + // I might need to inverse X axis here to get it facing the correct way, we'll see + + //Transform = mpawn->GetActorTransform().GetRelativeTransform(GetComponentTransform()); + } + } + else + { + // No PC, destroy the layer and enable drawing it normally. + bShouldCreateProxy = true; + + if (LayerId) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID; + } + return; + } + // + //Transform = GetRelativeTransform(); + } + } + + // If the transform changed dirty the layer and push the new transform + + if (!bIsDirty) + { + if (bLastVisible != bIsVisible) + { + bIsDirty = true; + } + else if (bDirtyRenderTarget || FMemory::Memcmp(&LastTransform, &Transform, sizeof(Transform)) != 0) + { + bIsDirty = true; + } + } + + bool bCurrVisible = bIsVisible; + if (!RenderTarget || !RenderTarget->GetResource()) + { + bCurrVisible = false; + } + + if (bIsDirty) + { + // OpenXR doesn't take the transforms scale component into account for the stereo layer, so we need to scale the buffer instead + // Fixed? In 5.2, leaving code commented out in case I need to bring it back + /*bool bScaleBuffer = false; + static FName SystemName(TEXT("OpenXR")); + if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName)) + { + bScaleBuffer = true; + }*/ + + IStereoLayers::FLayerDesc LayerDsec; + LayerDsec.Priority = Priority; + LayerDsec.QuadSize = FVector2D(DrawSize); + LayerDsec.UVRect = UVRect; + + if (bDelayForRenderThread && !LastTransform.Equals(FTransform::Identity)) + { + LayerDsec.Transform = LastTransform; + /*if (bScaleBuffer) + { + LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(LastTransform.GetScale3D()); + }*/ + } + else + { + LayerDsec.Transform = Transform; + /*if (bScaleBuffer) + { + LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(Transform.GetScale3D()); + }*/ + } + + if (RenderTarget) + { + LayerDsec.Texture = RenderTarget->GetResource()->TextureRHI; + LayerDsec.Flags |= (RenderTarget->GetMaterialType() == MCT_TextureExternal) ? IStereoLayers::LAYER_FLAG_TEX_EXTERNAL : 0; + } + // Forget the left texture implementation + //if (LeftTexture) + //{ + // LayerDsec.LeftTexture = LeftTexture->Resource->TextureRHI; + //} + + LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE;// (/*bLiveTexture*/true) ? IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE : 0; + LayerDsec.Flags |= (bNoAlphaChannel) ? IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL : 0; + LayerDsec.Flags |= (bQuadPreserveTextureRatio) ? IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO : 0; + LayerDsec.Flags |= (bSupportsDepth) ? IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH : 0; + LayerDsec.Flags |= (!bCurrVisible) ? IStereoLayers::LAYER_FLAG_HIDDEN : 0; + + // Fix this later when WorldLocked is no longer wrong. + switch (Space) + { + case EWidgetSpace::World: + { + if(bUseEpicsWorldLockedStereo) + LayerDsec.PositionType = IStereoLayers::WorldLocked; + else + LayerDsec.PositionType = IStereoLayers::TrackerLocked; + + //LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH; + }break; + + case EWidgetSpace::Screen: + default: + { + LayerDsec.PositionType = IStereoLayers::FaceLocked; + }break; + } + + switch (GeometryMode) + { + case EWidgetGeometryMode::Cylinder: + { + UStereoLayerShapeCylinder* Cylinder = Cast(Shape); + + if (!Cylinder) + { + if (Shape) + { + Shape->MarkAsGarbage(); + } + + Cylinder = NewObject(this, NAME_None, RF_Public); + Shape = Cylinder; + } + + if (Cylinder) + { + const float ArcAngleRadians = FMath::DegreesToRadians(CylinderArcAngle); + const float Radius = GetDrawSize().X / ArcAngleRadians; + + Cylinder->Height = GetDrawSize().Y;//CylinderHeight_DEPRECATED; + Cylinder->OverlayArc = CylinderArcAngle;// CylinderOverlayArc_DEPRECATED; + Cylinder->Radius = Radius;// CylinderRadius_DEPRECATED; + } + break; + + //LayerDsec.ShapeType = IStereoLayers::CylinderLayer; + + }break; + case EWidgetGeometryMode::Plane: + default: + { + UStereoLayerShapeQuad* Quad = Cast(Shape); + + if (!Quad) + { + if (Shape) + { + Shape->MarkAsGarbage(); + } + Shape = NewObject(this, NAME_None, RF_Public); + } + //LayerDsec.ShapeType = IStereoLayers::QuadLayer; + }break; + } + + if(Shape) + Shape->ApplyShape(LayerDsec); + + if (LayerId) + { + StereoLayers->SetLayerDesc(LayerId, LayerDsec); + } + else + { + LayerId = StereoLayers->CreateLayer(LayerDsec); + } + + } + + LastTransform = Transform; + bLastVisible = bCurrVisible; + bIsDirty = false; + bDirtyRenderTarget = false; +#endif +} + + +void UVRStereoWidgetComponent::SetPriority(int32 InPriority) +{ + if (Priority == InPriority) + { + return; + } + + Priority = InPriority; + bIsDirty = true; +} + +void UVRStereoWidgetComponent::UpdateRenderTarget(FIntPoint DesiredRenderTargetSize) +{ + Super::UpdateRenderTarget(DesiredRenderTargetSize); +} + +/** Represents a billboard sprite to the scene manager. */ +class FStereoWidget3DSceneProxy final : public FPrimitiveSceneProxy +{ +public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + /** Initialization constructor. */ + FStereoWidget3DSceneProxy(UVRStereoWidgetComponent* InComponent, ISlate3DRenderer& InRenderer) + : FPrimitiveSceneProxy(InComponent) + , Pivot(InComponent->GetPivot()) + , Renderer(InRenderer) + , RenderTarget(InComponent->GetRenderTarget()) + , MaterialInstance(InComponent->GetMaterialInstance()) + , BodySetup(InComponent->GetBodySetup()) + , BlendMode(InComponent->GetBlendMode()) + , GeometryMode(InComponent->GetGeometryMode()) + , ArcAngle(FMath::DegreesToRadians(InComponent->GetCylinderArcAngle())) + { + bWillEverBeLit = false; + bCreateSceneProxy = InComponent->bShouldCreateProxy; + MaterialRelevance = MaterialInstance->GetRelevance(GetScene().GetFeatureLevel()); + } + + // FPrimitiveSceneProxy interface. + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + if(!bCreateSceneProxy) + return; + +#if WITH_EDITOR + const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe; + + auto WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0, 0.5f, 1.f) + ); + + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + + FMaterialRenderProxy* ParentMaterialProxy = nullptr; + if (bWireframe) + { + ParentMaterialProxy = WireframeMaterialInstance; + } + else + { + ParentMaterialProxy = MaterialInstance->GetRenderProxy(); + } +#else + FMaterialRenderProxy* ParentMaterialProxy = MaterialInstance->GetRenderProxy(); +#endif + + //FSpriteTextureOverrideRenderProxy* TextureOverrideMaterialProxy = new FSpriteTextureOverrideRenderProxy(ParentMaterialProxy, + + const FMatrix& ViewportLocalToWorld = GetLocalToWorld(); + + FMatrix PreviousLocalToWorld; + + if (!GetScene().GetPreviousLocalToWorld(GetPrimitiveSceneInfo(), PreviousLocalToWorld)) + { + PreviousLocalToWorld = GetLocalToWorld(); + } + + if (RenderTarget) + { + FTextureResource* TextureResource = RenderTarget->GetResource(); + if (TextureResource) + { + if (GeometryMode == EWidgetGeometryMode::Plane) + { + float U = -RenderTarget->SizeX * Pivot.X; + float V = -RenderTarget->SizeY * Pivot.Y; + float UL = RenderTarget->SizeX * (1.0f - Pivot.X); + float VL = RenderTarget->SizeY * (1.0f - Pivot.Y); + + int32 VertexIndices[4]; + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel()); + + if (VisibilityMap & (1 << ViewIndex)) + { + VertexIndices[0] = MeshBuilder.AddVertex(-FVector3f(0, U, V), FVector2f(0, 0), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[1] = MeshBuilder.AddVertex(-FVector3f(0, U, VL), FVector2f(0, 1), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[2] = MeshBuilder.AddVertex(-FVector3f(0, UL, VL), FVector2f(1, 1), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[3] = MeshBuilder.AddVertex(-FVector3f(0, UL, V), FVector2f(1, 0), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]); + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]); + + FDynamicMeshBuilderSettings Settings; + Settings.bDisableBackfaceCulling = false; + Settings.bReceivesDecals = true; + Settings.bUseSelectionOutline = true; + MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId()); + } + } + } + else + { + ensure(GeometryMode == EWidgetGeometryMode::Cylinder); + + const int32 NumSegments = FMath::Lerp(4, 32, ArcAngle / PI); + + + const float Radius = RenderTarget->SizeX / ArcAngle; + const float Apothem = Radius * FMath::Cos(0.5f*ArcAngle); + const float ChordLength = 2.0f * Radius * FMath::Sin(0.5f*ArcAngle); + + const float PivotOffsetX = ChordLength * (0.5 - Pivot.X); + const float V = -RenderTarget->SizeY * Pivot.Y; + const float VL = RenderTarget->SizeY * (1.0f - Pivot.Y); + + int32 VertexIndices[4]; + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel()); + + if (VisibilityMap & (1 << ViewIndex)) + { + const float RadiansPerStep = ArcAngle / NumSegments; + + FVector LastTangentX; + FVector LastTangentY; + FVector LastTangentZ; + + for (int32 Segment = 0; Segment < NumSegments; Segment++) + { + const float Angle = -ArcAngle / 2 + Segment * RadiansPerStep; + const float NextAngle = Angle + RadiansPerStep; + + // Polar to Cartesian + const float X0 = Radius * FMath::Cos(Angle) - Apothem; + const float Y0 = Radius * FMath::Sin(Angle); + const float X1 = Radius * FMath::Cos(NextAngle) - Apothem; + const float Y1 = Radius * FMath::Sin(NextAngle); + + const float U0 = static_cast(Segment) / NumSegments; + const float U1 = static_cast(Segment + 1) / NumSegments; + + const FVector Vertex0 = -FVector(X0, PivotOffsetX + Y0, V); + const FVector Vertex1 = -FVector(X0, PivotOffsetX + Y0, VL); + const FVector Vertex2 = -FVector(X1, PivotOffsetX + Y1, VL); + const FVector Vertex3 = -FVector(X1, PivotOffsetX + Y1, V); + + FVector TangentX = Vertex3 - Vertex0; + TangentX.Normalize(); + FVector TangentY = Vertex1 - Vertex0; + TangentY.Normalize(); + FVector TangentZ = FVector::CrossProduct(TangentX, TangentY); + + if (Segment == 0) + { + LastTangentX = TangentX; + LastTangentY = TangentY; + LastTangentZ = TangentZ; + } + + VertexIndices[0] = MeshBuilder.AddVertex((FVector3f)Vertex0, FVector2f(U0, 0), (FVector3f)LastTangentX, (FVector3f)LastTangentY, (FVector3f)LastTangentZ, FColor::White); + VertexIndices[1] = MeshBuilder.AddVertex((FVector3f)Vertex1, FVector2f(U0, 1), (FVector3f)LastTangentX, (FVector3f)LastTangentY, (FVector3f)LastTangentZ, FColor::White); + VertexIndices[2] = MeshBuilder.AddVertex((FVector3f)Vertex2, FVector2f(U1, 1), (FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ, FColor::White); + VertexIndices[3] = MeshBuilder.AddVertex((FVector3f)Vertex3, FVector2f(U1, 0), (FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ, FColor::White); + + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]); + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]); + + LastTangentX = TangentX; + LastTangentY = TangentY; + LastTangentZ = TangentZ; + } + + FDynamicMeshBuilderSettings Settings; + Settings.bDisableBackfaceCulling = false; + Settings.bReceivesDecals = true; + Settings.bUseSelectionOutline = true; + MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId()); + } + } + } + } + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + RenderCollision(BodySetup, Collector, ViewIndex, ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); + RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); + } + } +#endif + } + + void RenderCollision(UBodySetup* InBodySetup, FMeshElementCollector& Collector, int32 ViewIndex, const FEngineShowFlags& EngineShowFlags, const FBoxSphereBounds& InBounds, bool bRenderInEditor) const + { + if (InBodySetup) + { + bool bDrawCollision = EngineShowFlags.Collision && IsCollisionEnabled(); + + if (bDrawCollision && AllowDebugViewmodes()) + { + // Draw simple collision as wireframe if 'show collision', collision is enabled, and we are not using the complex as the simple + const bool bDrawSimpleWireframeCollision = InBodySetup->CollisionTraceFlag != ECollisionTraceFlag::CTF_UseComplexAsSimple; + + if (FMath::Abs(GetLocalToWorld().Determinant()) < UE_SMALL_NUMBER) + { + // Catch this here or otherwise GeomTransform below will assert + // This spams so commented out + //UE_LOG(LogStaticMesh, Log, TEXT("Zero scaling not supported (%s)"), *StaticMesh->GetPathName()); + } + else + { + const bool bDrawSolid = !bDrawSimpleWireframeCollision; + const bool bProxyIsSelected = IsSelected(); + + if (bDrawSolid) + { + // Make a material for drawing solid collision stuff + auto SolidMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(), + GetWireframeColor() + ); + + Collector.RegisterOneFrameMaterialProxy(SolidMaterialInstance); + + FTransform GeomTransform(GetLocalToWorld()); + InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetWireframeColor().ToFColor(true), SolidMaterialInstance, false, true, AlwaysHasVelocity(), ViewIndex, Collector); + } + // wireframe + else + { + FColor CollisionColor = FColor(157, 149, 223, 255); + FTransform GeomTransform(GetLocalToWorld()); + InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(CollisionColor, bProxyIsSelected, IsHovered()).ToFColor(true), nullptr, false, false, AlwaysHasVelocity(), ViewIndex, Collector); + } + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + bool bVisible = true; + + FPrimitiveViewRelevance Result; + + MaterialRelevance.SetPrimitiveViewRelevance(Result); + + Result.bDrawRelevance = IsShown(View) && bVisible && View->Family->EngineShowFlags.WidgetComponents; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bEditorPrimitiveRelevance = false; + Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; + } + + virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override + { + bDynamic = false; + bRelevant = false; + bLightMapped = false; + bShadowMapped = false; + } + + virtual void OnTransformChanged() override + { + Origin = GetLocalToWorld().GetOrigin(); + } + + 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: + FVector Origin; + FVector2D Pivot; + ISlate3DRenderer& Renderer; + UTextureRenderTarget2D* RenderTarget; + UMaterialInstanceDynamic* MaterialInstance; + FMaterialRelevance MaterialRelevance; + UBodySetup* BodySetup; + EWidgetBlendMode BlendMode; + EWidgetGeometryMode GeometryMode; + float ArcAngle; + bool bCreateSceneProxy; +}; + + +FPrimitiveSceneProxy* UVRStereoWidgetComponent::CreateSceneProxy() +{ + if (Space == EWidgetSpace::Screen) + { + return nullptr; + } + + if (WidgetRenderer && GetSlateWindow() && GetSlateWindow()->GetContent() != SNullWidget::NullWidget) + { + RequestRenderUpdate(); + LastWidgetRenderTime = 0; + + return new FStereoWidget3DSceneProxy(this, *WidgetRenderer->GetSlateRenderer()); + } + +#if WITH_EDITOR + // make something so we can see this component in the editor + class FWidgetBoxProxy final : public FPrimitiveSceneProxy + { + public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + FWidgetBoxProxy(const UWidgetComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , BoxExtents(1.f, InComponent->GetCurrentDrawSize().X / 2.0f, InComponent->GetCurrentDrawSize().Y / 2.0f) + { + bWillEverBeLit = false; + } + + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_BoxSceneProxy_GetDynamicMeshElements); + + const FMatrix& LocalToWorld = GetLocalToWorld(); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + + const FLinearColor DrawColor = GetViewSelectionColor(FColor::White, *View, IsSelected(), IsHovered(), false, IsIndividuallySelected()); + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + DrawOrientedWireBox(PDI, LocalToWorld.GetOrigin(), LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), BoxExtents, DrawColor, SDPG_World); + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + if (!View->bIsGameView) + { + // Should we draw this because collision drawing is enabled, and we have collision + const bool bShowForCollision = View->Family->EngineShowFlags.Collision && IsCollisionEnabled(); + Result.bDrawRelevance = IsShown(View) || bShowForCollision; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + } + return Result; + } + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + + private: + const FVector BoxExtents; + }; + + return new FWidgetBoxProxy(this); +#else + return nullptr; +#endif +} + +class FVRStereoWidgetComponentInstanceData : public FActorComponentInstanceData +{ +public: + FVRStereoWidgetComponentInstanceData(const UVRStereoWidgetComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) + , RenderTarget(SourceComponent->GetRenderTarget()) + {} + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + FActorComponentInstanceData::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyVRComponentInstanceData(this); + } + + /*virtual void AddReferencedObjects(FReferenceCollector& Collector) override + { + FActorComponentInstanceData::AddReferencedObjects(Collector); + + UClass* WidgetUClass = *WidgetClass; + Collector.AddReferencedObject(WidgetUClass); + Collector.AddReferencedObject(RenderTarget); + }*/ + +public: + UTextureRenderTarget2D* RenderTarget; +}; + +TStructOnScope UVRStereoWidgetComponent::GetComponentInstanceData() const +{ + return MakeStructOnScope(this); +} + +void UVRStereoWidgetComponent::ApplyVRComponentInstanceData(FVRStereoWidgetComponentInstanceData* WidgetInstanceData) +{ + check(WidgetInstanceData); + + // Note: ApplyComponentInstanceData is called while the component is registered so the rendering thread is already using this component + // That means all component state that is modified here must be mirrored on the scene proxy, which will be recreated to receive the changes later due to MarkRenderStateDirty. + + if (GetWidgetClass() != WidgetClass) + { + return; + } + + RenderTarget = WidgetInstanceData->RenderTarget; + + // Also set the texture + //Texture = RenderTarget; + // Not needed anymore, just using the render target directly now + + if (MaterialInstance && RenderTarget) + { + MaterialInstance->SetTextureParameterValue("SlateUI", RenderTarget); + } + + MarkRenderStateDirty(); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp new file mode 100644 index 0000000..ea80798 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp @@ -0,0 +1,72 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRTrackedParentInterface.h" +#include UE_INLINE_GENERATED_CPP_BY_NAME(VRTrackedParentInterface) + +#include "UObject/Interface.h" +#include "VRBPDatatypes.h" + +UVRTrackedParentInterface::UVRTrackedParentInterface(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +void IVRTrackedParentInterface::Default_SetTrackedParent_Impl(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode, FBPVRWaistTracking_Info & OptionalWaistTrackingParent, USceneComponent * Self) +{ + // If had a different original tracked parent + // Moved this to first thing so the pre-res is removed prior to erroring out and clearing this + if (OptionalWaistTrackingParent.IsValid()) + { + // Remove the tick Prerequisite + Self->RemoveTickPrerequisiteComponent(OptionalWaistTrackingParent.TrackedDevice); + } + if (!NewParentComponent || !Self) + { + OptionalWaistTrackingParent.Clear(); + return; + } + // Make other component tick first if possible, waste of time if in wrong tick group + if (NewParentComponent->PrimaryComponentTick.TickGroup == Self->PrimaryComponentTick.TickGroup) + { + // Make sure the other component isn't depending on this one + NewParentComponent->RemoveTickPrerequisiteComponent(Self); + // Add a tick pre-res for ourselves so that we tick after our tracked parent. + Self->AddTickPrerequisiteComponent(NewParentComponent); + } + OptionalWaistTrackingParent.TrackedDevice = NewParentComponent; + OptionalWaistTrackingParent.RestingRotation = NewParentComponent->GetRelativeRotation(); + OptionalWaistTrackingParent.RestingRotation.Yaw = 0.0f; + + OptionalWaistTrackingParent.TrackingMode = WaistTrackingMode; + OptionalWaistTrackingParent.WaistRadius = WaistRadius; +} +FTransform IVRTrackedParentInterface::Default_GetWaistOrientationAndPosition(FBPVRWaistTracking_Info & WaistTrackingInfo) +{ + if (!WaistTrackingInfo.IsValid()) + return FTransform::Identity; + FTransform DeviceTransform = WaistTrackingInfo.TrackedDevice->GetRelativeTransform(); + // Rewind by the initial rotation when the new parent was set, this should be where the tracker rests on the person + DeviceTransform.ConcatenateRotation(WaistTrackingInfo.RestingRotation.Quaternion().Inverse()); + DeviceTransform.SetScale3D(FVector(1, 1, 1)); + // Don't bother if not set + if (WaistTrackingInfo.WaistRadius > 0.0f) + { + DeviceTransform.AddToTranslation(DeviceTransform.GetRotation().RotateVector(FVector(-WaistTrackingInfo.WaistRadius, 0, 0))); + } + // This changes the forward vector to be correct + // I could pre do it by changed the yaw in resting mode to these values, but that had its own problems + // If given an initial forward vector that it should align to I wouldn't have to do this and could auto calculate it. + // But without that I am limited to this. + // #TODO: add optional ForwardVector to initial setup function that auto calculates offset so that the user can pass in HMD forward or something for calibration X+ + // Also would be better overall because slightly offset from right angles in yaw wouldn't matter anymore, it would adjust for it. + switch (WaistTrackingInfo.TrackingMode) + { + case EBPVRWaistTrackingMode::VRWaist_Tracked_Front: DeviceTransform.ConcatenateRotation(FRotator(0, 0, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Rear: DeviceTransform.ConcatenateRotation(FRotator(0, -180, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Left: DeviceTransform.ConcatenateRotation(FRotator(0, 90, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Right: DeviceTransform.ConcatenateRotation(FRotator(0, -90, 0).Quaternion()); break; + } + + return DeviceTransform; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h new file mode 100644 index 0000000..3afc56a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h @@ -0,0 +1,747 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "GameFramework/Character.h" +#include "Components/SkeletalMeshComponent.h" +#include "Engine/ScopedMovementUpdate.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "CharacterMovementCompTypes.generated.h" + + +class AVRBaseCharacter; +class UVRBaseCharacterMovementComponent; + +UENUM(Blueprintable) +enum class EVRMoveAction : uint8 +{ + VRMOVEACTION_None = 0x00, + VRMOVEACTION_SnapTurn = 0x01, + VRMOVEACTION_Teleport = 0x02, + VRMOVEACTION_StopAllMovement = 0x03, + VRMOVEACTION_SetRotation = 0x04, + VRMOVEACTION_PauseTracking = 0x14, + VRMOVEACTION_SetGravityDirection = 0x15, // Reserved from here up to 0x40 + VRMOVEACTION_CUSTOM1 = 0x05, + VRMOVEACTION_CUSTOM2 = 0x06, + VRMOVEACTION_CUSTOM3 = 0x07, + VRMOVEACTION_CUSTOM4 = 0x08, + VRMOVEACTION_CUSTOM5 = 0x09, + VRMOVEACTION_CUSTOM6 = 0x0A, + VRMOVEACTION_CUSTOM7 = 0x0B, + VRMOVEACTION_CUSTOM8 = 0x0C, + VRMOVEACTION_CUSTOM9 = 0x0D, + VRMOVEACTION_CUSTOM10 = 0x0E, + VRMOVEACTION_CUSTOM11 = 0x0F, + VRMOVEACTION_CUSTOM12 = 0x10, + VRMOVEACTION_CUSTOM13 = 0x11, + VRMOVEACTION_CUSTOM14 = 0x12, + VRMOVEACTION_CUSTOM15 = 0x13, + // Up to 0x20 currently allowed for +}; + +// What to do with the players velocity when specific move actions are called +// Default of none leaves it as is, for people with 0 ramp up time on acelleration +// This likely won't be that useful. +UENUM(Blueprintable) +enum class EVRMoveActionVelocityRetention : uint8 +{ + // Leaves velocity as is + VRMOVEACTION_Velocity_None = 0x00, + + // Clears velocity entirely + VRMOVEACTION_Velocity_Clear = 0x01, + + // Rotates the velocity to match new heading + VRMOVEACTION_Velocity_Turn = 0x02 +}; + +UENUM(Blueprintable) +enum class EVRMoveActionDataReq : uint8 +{ + VRMOVEACTIONDATA_None = 0x00, + VRMOVEACTIONDATA_LOC = 0x01, + VRMOVEACTIONDATA_ROT = 0x02, + VRMOVEACTIONDATA_LOC_AND_ROT = 0x03 +}; + + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRMoveActionContainer +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + EVRMoveAction MoveAction; + UPROPERTY() + EVRMoveActionDataReq MoveActionDataReq; + UPROPERTY() + FVector MoveActionLoc; + UPROPERTY() + FVector MoveActionVel; + UPROPERTY() + FRotator MoveActionRot; + UPROPERTY() + float MoveActionDeltaYaw; + UPROPERTY() + uint8 MoveActionFlags; + UPROPERTY() + TArray MoveActionObjectReferences; + UPROPERTY() + EVRMoveActionVelocityRetention VelRetentionSetting; + + FVRMoveActionContainer() + { + Clear(); + } + + void Clear() + { + MoveAction = EVRMoveAction::VRMOVEACTION_None; + MoveActionDataReq = EVRMoveActionDataReq::VRMOVEACTIONDATA_None; + MoveActionLoc = FVector::ZeroVector; + MoveActionVel = FVector::ZeroVector; + MoveActionRot = FRotator::ZeroRotator; + MoveActionDeltaYaw = 0.0f; + MoveActionFlags = 0; + VelRetentionSetting = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None; + MoveActionObjectReferences.Empty(); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + Ar.SerializeBits(&MoveAction, 6); // 64 elements, only allowing 1 per frame, they aren't flags + + switch (MoveAction) + { + case EVRMoveAction::VRMOVEACTION_None: break; + case EVRMoveAction::VRMOVEACTION_SetRotation: + case EVRMoveAction::VRMOVEACTION_SnapTurn: + { + uint16 Yaw = 0; + uint16 Pitch = 0; + + if (Ar.IsSaving()) + { + bool bUseLocOnly = MoveActionFlags & 0x04; + Ar.SerializeBits(&bUseLocOnly, 1); + + if (!bUseLocOnly) + { + //Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + //Ar << Yaw; + Ar << MoveActionRot; + } + else + { + Ar << MoveActionLoc; + } + + bool bTeleportGrips = MoveActionFlags & 0x01;// MoveActionRot.Roll > 0.0f && MoveActionRot.Roll < 1.5f; + Ar.SerializeBits(&bTeleportGrips, 1); + + if (!bTeleportGrips) + { + bool bTeleportCharacter = MoveActionFlags & 0x02;// MoveActionRot.Roll > 1.5f; + Ar.SerializeBits(&bTeleportCharacter, 1); + } + + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + } + + // Always send this in case we are sitting + Pitch = FRotator::CompressAxisToShort(MoveActionDeltaYaw); + Ar << Pitch; + + bool bRotateAroundCapsule = MoveActionFlags & 0x08; + Ar.SerializeBits(&bRotateAroundCapsule, 1); + } + else + { + + bool bUseLocOnly = false; + Ar.SerializeBits(&bUseLocOnly, 1); + MoveActionFlags |= (bUseLocOnly << 2); + + if (!bUseLocOnly) + { + //Ar << Yaw; + //MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + Ar << MoveActionRot; + } + else + { + Ar << MoveActionLoc; + } + + bool bTeleportGrips = false; + Ar.SerializeBits(&bTeleportGrips, 1); + MoveActionFlags |= (uint8)bTeleportGrips; //.Roll = bTeleportGrips ? 1.0f : 0.0f; + + if (!bTeleportGrips) + { + bool bTeleportCharacter = false; + Ar.SerializeBits(&bTeleportCharacter, 1); + MoveActionFlags |= ((uint8)bTeleportCharacter << 1); + //MoveActionRot.Roll = 2.0f; + } + + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + } + + Ar << Pitch; + MoveActionDeltaYaw = FRotator::DecompressAxisFromShort(Pitch); + + bool bRotateAroundCapsule = false; + Ar.SerializeBits(&bRotateAroundCapsule, 1); + MoveActionFlags |= (uint8)(bRotateAroundCapsule << 3); + } + + //bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + }break; + case EVRMoveAction::VRMOVEACTION_Teleport: // Not replicating rot as Control rot does that already + { + uint16 Yaw = 0; + uint16 Pitch = 0; + + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + Ar << Yaw; + + bool bSkipEncroachment = MoveActionFlags & 0x01;// MoveActionRot.Roll > 0.0f; + Ar.SerializeBits(&bSkipEncroachment, 1); + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Pitch = FRotator::CompressAxisToShort(MoveActionRot.Pitch); + //Ar << Pitch; + } + } + else + { + Ar << Yaw; + MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + + bool bSkipEncroachment = false; + Ar.SerializeBits(&bSkipEncroachment, 1); + MoveActionFlags |= (uint8)bSkipEncroachment; + //MoveActionRot.Roll = bSkipEncroachment ? 1.0f : 0.0f; + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Ar << Pitch; + //MoveActionRot.Pitch = FRotator::DecompressAxisFromShort(Pitch); + } + } + + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + }break; + case EVRMoveAction::VRMOVEACTION_StopAllMovement: + {}break; + case EVRMoveAction::VRMOVEACTION_SetGravityDirection: + { + bOutSuccess = SerializeFixedVector<1, 16>(MoveActionVel, Ar); + if (Ar.IsSaving()) + { + bool bOrientToGravity = MoveActionFlags > 0; + Ar.SerializeBits(&bOrientToGravity, 1); + } + else + { + bool bOrientToGravity = false; + Ar.SerializeBits(&bOrientToGravity, 1); + MoveActionFlags |= (uint8)bOrientToGravity; + } + + }break; + case EVRMoveAction::VRMOVEACTION_PauseTracking: + { + + Ar.SerializeBits(&MoveActionFlags, 1); + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + + uint16 Yaw = 0; + // Loc and rot for capsule should also be sent here + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + Ar << Yaw; + } + else + { + Ar << Yaw; + MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + } + + }break; + default: // Everything else + { + // Defines how much to replicate - only 4 possible values, 0 - 3 so only send 2 bits + Ar.SerializeBits(&MoveActionDataReq, 2); + + if (((uint8)MoveActionDataReq & (uint8)EVRMoveActionDataReq::VRMOVEACTIONDATA_LOC) != 0) + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + + if (((uint8)MoveActionDataReq & (uint8)EVRMoveActionDataReq::VRMOVEACTIONDATA_ROT) != 0) + MoveActionRot.SerializeCompressedShort(Ar); + + bool bSerializeObjects = MoveActionObjectReferences.Num() > 0; + Ar.SerializeBits(&bSerializeObjects, 1); + if (bSerializeObjects) + { + Ar << MoveActionObjectReferences; + } + + bool bSerializeFlags = MoveActionFlags != 0x00; + Ar.SerializeBits(&bSerializeFlags, 1); + if (bSerializeFlags) + { + Ar << MoveActionFlags; + } + + }break; + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRMoveActionContainer > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRMoveActionArray +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + TArray MoveActions; + + bool CanCombine() const + { + return !MoveActions.Num(); + /*if (!MoveActions.Num()) + { + return true; + } + + for (const FVRMoveActionContainer& MoveAction : MoveActions) + { + if (MoveAction.MoveAction != EVRMoveAction::VRMOVEACTION_SnapTurn) + return false; + } + + return true;*/ + } + + void Clear() + { + MoveActions.Empty(); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + uint8 MoveActionCount = (uint8)MoveActions.Num(); + bool bHasAMoveAction = MoveActionCount > 0; + Ar.SerializeBits(&bHasAMoveAction, 1); + + if (bHasAMoveAction) + { + bool bHasMoreThanOneMoveAction = MoveActionCount > 1; + Ar.SerializeBits(&bHasMoreThanOneMoveAction, 1); + + if (Ar.IsSaving()) + { + if (bHasMoreThanOneMoveAction) + { + Ar << MoveActionCount; + + for (int i = 0; i < MoveActionCount; i++) + { + bOutSuccess &= MoveActions[i].NetSerialize(Ar, Map, bOutSuccess); + } + } + else + { + bOutSuccess &= MoveActions[0].NetSerialize(Ar, Map, bOutSuccess); + } + } + else + { + if (bHasMoreThanOneMoveAction) + { + Ar << MoveActionCount; + } + else + MoveActionCount = 1; + + for (int i = 0; i < MoveActionCount; i++) + { + FVRMoveActionContainer MoveAction; + bOutSuccess &= MoveAction.NetSerialize(Ar, Map, bOutSuccess); + MoveActions.Add(MoveAction); + } + } + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRMoveActionArray > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRConditionalMoveRep +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(Transient) + FVector CustomVRInputVector; + UPROPERTY(Transient) + FVector RequestedVelocity; + UPROPERTY(Transient) + FVRMoveActionArray MoveActionArray; + //FVRMoveActionContainer MoveAction; + + FVRConditionalMoveRep() + { + CustomVRInputVector = FVector::ZeroVector; + RequestedVelocity = FVector::ZeroVector; + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + bool bIsLoading = Ar.IsLoading(); + + bool bHasVRinput = !CustomVRInputVector.IsZero(); + bool bHasRequestedVelocity = !RequestedVelocity.IsZero(); + bool bHasMoveAction = MoveActionArray.MoveActions.Num() > 0;//MoveAction.MoveAction != EVRMoveAction::VRMOVEACTION_None; + + bool bHasAnyProperties = bHasVRinput || bHasRequestedVelocity || bHasMoveAction; + Ar.SerializeBits(&bHasAnyProperties, 1); + + if (bHasAnyProperties) + { + Ar.SerializeBits(&bHasVRinput, 1); + Ar.SerializeBits(&bHasRequestedVelocity, 1); + //Ar.SerializeBits(&bHasMoveAction, 1); + + if (bHasVRinput) + { + bOutSuccess &= SerializePackedVector<100, 22/*30*/>(CustomVRInputVector, Ar); + } + else if (bIsLoading) + { + CustomVRInputVector = FVector::ZeroVector; + } + + if (bHasRequestedVelocity) + { + bOutSuccess &= SerializePackedVector<100, 22/*30*/>(RequestedVelocity, Ar); + } + else if (bIsLoading) + { + RequestedVelocity = FVector::ZeroVector; + } + + //if (bHasMoveAction) + MoveActionArray.NetSerialize(Ar, Map, bOutSuccess); + } + else if (bIsLoading) + { + CustomVRInputVector = FVector::ZeroVector; + RequestedVelocity = FVector::ZeroVector; + MoveActionArray.Clear(); + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FVRConditionalMoveRep > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +// #TODO: DELETE THIS +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRConditionalMoveRep2 +{ + GENERATED_USTRUCT_BODY() +public: + + // Moved these here to avoid having to duplicate tons of properties + UPROPERTY(Transient) + UPrimitiveComponent* ClientMovementBase; + UPROPERTY(Transient) + FName ClientBaseBoneName; + + UPROPERTY(Transient) + uint16 ClientYaw; + + UPROPERTY(Transient) + uint16 ClientPitch; + + UPROPERTY(Transient) + uint8 ClientRoll; + + FVRConditionalMoveRep2() + { + ClientMovementBase = nullptr; + ClientBaseBoneName = NAME_None; + ClientRoll = 0; + ClientPitch = 0; + ClientYaw = 0; + } + + void UnpackAndSetINTRotations(uint32 Rotation32) + { + // Reversed the order of these so it costs less to replicate + ClientYaw = (Rotation32 & 65535); + ClientPitch = (Rotation32 >> 16); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + bool bRepRollAndPitch = (ClientRoll != 0 || ClientPitch != 0); + Ar.SerializeBits(&bRepRollAndPitch, 1); + + if (bRepRollAndPitch) + { + // Reversed the order of these + uint32 Rotation32 = (((uint32)ClientPitch) << 16) | ((uint32)ClientYaw); + Ar.SerializeIntPacked(Rotation32); + Ar << ClientRoll; + + if (Ar.IsLoading()) + { + UnpackAndSetINTRotations(Rotation32); + } + } + else + { + uint32 Yaw32 = ClientYaw; + Ar.SerializeIntPacked(Yaw32); + ClientYaw = (uint16)Yaw32; + } + + bool bHasMovementBase = MovementBaseUtility::IsDynamicBase(ClientMovementBase); + Ar.SerializeBits(&bHasMovementBase, 1); + + if (bHasMovementBase) + { + Ar << ClientMovementBase; + + bool bValidName = ClientBaseBoneName != NAME_None; + Ar.SerializeBits(&bValidName, 1); + + // This saves 9 bits on average, we almost never have a valid bone name and default rep goes to 9 bits for hardcoded + // total of 6 bits savings as we use 3 extra for our flags in here. + if (bValidName) + { + Ar << ClientBaseBoneName; + } + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FVRConditionalMoveRep2 > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +/** +* Helper to change mesh bone updates within a scope. +* Example usage: +* { +* FScopedPreventMeshBoneUpdate ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones); +* // Do something to move mesh, bones will not update +* } +* // Movement of mesh at this point will use previous setting. +*/ +struct FScopedMeshBoneUpdateOverrideVR +{ + FScopedMeshBoneUpdateOverrideVR(USkeletalMeshComponent* Mesh, EKinematicBonesUpdateToPhysics::Type OverrideSetting); + + ~FScopedMeshBoneUpdateOverrideVR(); + +private: + USkeletalMeshComponent* MeshRef; + EKinematicBonesUpdateToPhysics::Type SavedUpdateSetting; +}; + + +class VREXPANSIONPLUGIN_API FSavedMove_VRBaseCharacter : public FSavedMove_Character +{ + +public: + + EVRConjoinedMovementModes VRReplicatedMovementMode; + + FVector VRCapsuleLocation; + FVector LFDiff; + FRotator VRCapsuleRotation; + float CapsuleHeight; + FVRConditionalMoveRep ConditionalValues; + + void Clear(); + virtual void SetInitialPosition(ACharacter* C); + virtual void PrepMoveFor(ACharacter* Character) override; + virtual void CombineWith(const FSavedMove_Character* OldMove, ACharacter* InCharacter, APlayerController* PC, const FVector& OldStartLocation) override; + + /** Set the properties describing the final position, etc. of the moved pawn. */ + virtual void PostUpdate(ACharacter* C, EPostUpdateMode PostUpdateMode) override; + + FSavedMove_VRBaseCharacter(); + + virtual uint8 GetCompressedFlags() const override; + virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const override; + virtual bool IsImportantMove(const FSavedMovePtr& LastAckedMove) const override; +}; + +// Using this fixes the problem where the character capsule isn't reset after a scoped movement update revert (pretty much just in StepUp operations) +class VREXPANSIONPLUGIN_API FVRCharacterScopedMovementUpdate : public FScopedMovementUpdate +{ +public: + + FVRCharacterScopedMovementUpdate(USceneComponent* Component, EScopedUpdate::Type ScopeBehavior = EScopedUpdate::DeferredUpdates, bool bRequireOverlapsEventFlagToQueueOverlaps = true); + + FTransform InitialVRTransform; + + /** Revert movement to the initial location of the Component at the start of the scoped update. Also clears pending overlaps and sets bHasMoved to false. */ + void RevertMove(); +}; + + +/** Shared pointer for easy memory management of FSavedMove_Character, for accumulating and replaying network moves. */ +//typedef TSharedPtr FSavedMovePtr; +struct VREXPANSIONPLUGIN_API FVRCharacterNetworkMoveData : public FCharacterNetworkMoveData +{ +public: + + FVector_NetQuantize100 VRCapsuleLocation; + FVector/*_NetQuantize100*/ LFDiff; + float CapsuleHeight; + uint16 VRCapsuleRotation; + EVRConjoinedMovementModes ReplicatedMovementMode; + FVRConditionalMoveRep ConditionalMoveReps; + + FVRCharacterNetworkMoveData(); + + virtual ~FVRCharacterNetworkMoveData(); + virtual void ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) override; + virtual bool Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) override; +}; + +struct VREXPANSIONPLUGIN_API FVRCharacterNetworkMoveDataContainer : public FCharacterNetworkMoveDataContainer +{ +public: + + /** + * Default constructor. Sets data storage (NewMoveData, PendingMoveData, OldMoveData) to point to default data members. Override those pointers to instead point to custom data if you want to use derived classes. + */ + FVRCharacterNetworkMoveDataContainer() : FCharacterNetworkMoveDataContainer() + { + NewMoveData = &VRBaseDefaultMoveData[0]; + PendingMoveData = &VRBaseDefaultMoveData[1]; + OldMoveData = &VRBaseDefaultMoveData[2]; + } + + virtual ~FVRCharacterNetworkMoveDataContainer() + { + } + + /** + * Passes through calls to ClientFillNetworkMoveData on each FCharacterNetworkMoveData matching the client moves. Note that ClientNewMove will never be null, but others may be. + */ + //virtual void ClientFillNetworkMoveData(const FSavedMove_Character* ClientNewMove, const FSavedMove_Character* ClientPendingMove, const FSavedMove_Character* ClientOldMove); + + /** + * Serialize movement data. Passes Serialize calls to each FCharacterNetworkMoveData as applicable, based on bHasPendingMove and bHasOldMove. + */ + //virtual bool Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap); + + + +protected: + + + FVRCharacterNetworkMoveData VRBaseDefaultMoveData[3]; + +}; + +struct VREXPANSIONPLUGIN_API FVRCharacterMoveResponseDataContainer : public FCharacterMoveResponseDataContainer +{ +public: + + FVRCharacterMoveResponseDataContainer() : FCharacterMoveResponseDataContainer() + { + } + + virtual ~FVRCharacterMoveResponseDataContainer() + { + } + + /** + * Copy the FClientAdjustment and set a few flags relevant to that data. + */ + virtual void ServerFillResponseData(const UCharacterMovementComponent& CharacterMovement, const FClientAdjustment& PendingAdjustment) override; + + //bool bHasRotation; // By default ClientAdjustment.NewRot is not serialized. Set this to true after base ServerFillResponseData if you want Rotation to be serialized. + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h new file mode 100644 index 0000000..a6fb882 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h @@ -0,0 +1,1582 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "Engine/EngineBaseTypes.h" +#include "SceneViewExtension.h" +#include "VRBPDatatypes.h" +#include "MotionControllerComponent.h" +#include "VRGripInterface.h" +#include "GripScripts/VRGripScriptBase.h" +#include "GripMotionControllerComponent.generated.h" + +class AVRBaseCharacter; +class AVRCharacter; +struct FXRDeviceId; + +/** +* +*/ + +/** Override replication control variable for inherited properties that are private. Be careful since it removes a compile-time error when the variable doesn't exist */ +// This is a temp macro until epic adds their own equivalent +// #UE5.2 - Epic seems to allow the std override macro to work on private properties now, commented this out in case its needed later +/*#define DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(c,v,active) \ +{ \ + static FProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),FName(TEXT(#v))); \ + for (int32 i = 0; i < sp##v->ArrayDim; i++) \ + { \ + UE::Net::Private::FNetPropertyConditionManager::SetPropertyActiveOverride(ChangedPropertyTracker, this, (int32)c::ENetFields_Private::v, active); \ + } \ +}*/ + +#define RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(c,v,cond) ResetReplicatedLifetimeProperty(StaticClass(), c::StaticClass(), FName(TEXT(#v)), cond, OutLifetimeProps); + +DECLARE_LOG_CATEGORY_EXTERN(LogVRMotionController, Log, All); +//For UE4 Profiler ~ Stat Group +DECLARE_STATS_GROUP(TEXT("TICKGrip"), STATGROUP_TickGrip, STATCAT_Advanced); + +/** Delegate for notification when the controllers tracking changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRGripControllerOnTrackingEventSignature, const ETrackingStatus &, NewTrackingStatus); + +/** Delegate for notification when the controller grips a new object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVROnControllerGripSignature, const FBPActorGripInformation &, GripInformation); + +/** Delegate for notification when the controller drops a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnControllerDropSignature, const FBPActorGripInformation &, GripInformation, bool, bWasSocketed); + +/** Delegate for notification when the controller sockets a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FVROnControllerSocketSignature, const FBPActorGripInformation&, GripInformation, const USceneComponent*, NewParentComp, FName, OptionalSocketName, FTransform, RelativeTransformToParent, bool, bWeldingBodies); + +/** Delegate for notification when the controller teleports its grips. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVROnControllerTeleportedGripsSignature); + +/** Delegate for notification when an interactive grip goes out of range and isn't set to auto handle it. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRGripControllerOnGripOutOfRange, const FBPActorGripInformation &, GripInformation, float, Distance); + +/** Delegate for notification when the controller profile transform changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRGripControllerOnProfileTransformChanged, const FTransform &, NewRelTransForProcComps, const FTransform &, NewProfileTransform); + +/** Delegate for notification when the controller handled a local auth grip conflict. Only called on the server. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnClientAuthGripConflict, UObject *, Object, EVRClientAuthConflictResolutionMode, ResolutionMethod); + +/** +* Utility class for applying an offset to a hierarchy of components in the renderer thread. +*/ +class VREXPANSIONPLUGIN_API FExpandedLateUpdateManager +{ +public: + FExpandedLateUpdateManager(); + + virtual ~FExpandedLateUpdateManager() {} + + /** Setup state for applying the render thread late update */ + void Setup(const FTransform& ParentToWorld, UGripMotionControllerComponent* Component, bool bSkipLateUpdate); + + /** Apply the late update delta to the cached components */ + void Apply_RenderThread(FSceneInterface* Scene, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform); + // #TODO: UE5 is missing this pull + //void Apply_RenderThread(FSceneInterface* Scene, const int32 FrameNumber, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform); + + /** Returns true if the LateUpdateSetup data is stale. */ + bool GetSkipLateUpdate_RenderThread() const { return UpdateStates[LateUpdateRenderReadIndex].bSkip; } + +public: + + /** A utility method that calls CacheSceneInfo on ParentComponent and all of its descendants */ + void GatherLateUpdatePrimitives(USceneComponent* ParentComponent); + void ProcessGripArrayLateUpdatePrimitives(UGripMotionControllerComponent* MotionController, TArray & GripArray); + + /** Generates a LateUpdatePrimitiveInfo for the given component if it has a SceneProxy and appends it to the current LateUpdatePrimitives array */ + void CacheSceneInfo(USceneComponent* Component); + + struct FLateUpdateState + { + FLateUpdateState() + : ParentToWorld(FTransform::Identity) + , bSkip(false) + , TrackingNumber(-1) + {} + + /** Parent world transform used to reconstruct new world transforms for late update scene proxies */ + FTransform ParentToWorld; + /** Primitives that need late update before rendering */ + TMap Primitives; + /** Late Update Info Stale, if this is found true do not late update */ + bool bSkip; + /** Frame tracking number - used to flag if the game and render threads get badly out of sync */ + int64 TrackingNumber; + }; + + FLateUpdateState UpdateStates[2]; + int32 LateUpdateGameWriteIndex; + int32 LateUpdateRenderReadIndex; +}; + +/** +* Tick function that does post physics work. This executes in EndPhysics (after physics is done) +**/ +USTRUCT() +struct FGripComponentEndPhysicsTickFunction : public FTickFunction +{ + GENERATED_USTRUCT_BODY() + + UGripMotionControllerComponent* Target; + + /** + * Abstract function to execute the tick. + * @param DeltaTime - frame time to advance, in seconds. + * @param TickType - kind of tick for this frame. + * @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created. + * @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete. + */ + virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; + /** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph. */ + virtual FString DiagnosticMessage() override; + /** Function used to describe this tick for active tick reporting. **/ + virtual FName DiagnosticContext(bool bDetailed) override; +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithCopy = false + }; +}; + +/** +* An override of the MotionControllerComponent that implements position replication and Gripping with grip replication and controllable late updates per object. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = MotionController) +class VREXPANSIONPLUGIN_API UGripMotionControllerComponent : public UMotionControllerComponent//PrimitiveComponent +{ + +public: + + FGripComponentEndPhysicsTickFunction EndPhysicsTickFunction; + friend struct FGripComponentEndPhysicsTickFunction; + + /** Update systems after physics sim is done */ + void EndPhysicsTickComponent(FGripComponentEndPhysicsTickFunction& ThisTickFunction); + void RegisterEndPhysicsTick(bool bRegister); + + // If true then we will sample the post physics scene to get the relative location of this object. + // This lets us reproject that relative position prior to running the grip logic. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GripMotionController|Advanced") + bool bProjectNonSimulatingGrips; + + // If true then we will sweep grip teleport operations so that they stop when they will be colliding with something. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GripMotionController|Advanced") + bool bSweepGripTeleports = false; + + + // The grip script that defines the default behaviors of grips + // Don't edit this unless you really know what you are doing, leave it empty + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GripMotionController|Advanced") + TSubclassOf DefaultGripScriptClass; + + // This is the pointer to the default grip script + // It is here to access so if you want to set some variables on your override then you can + // Due to a bug with instanced variables and parent classes you can't directly edit this in subclass in the details panel + UPROPERTY(VisibleDefaultsOnly, Transient, BlueprintReadOnly, Category = "GripMotionController|Advanced") + TObjectPtr DefaultGripScript; + + // This is a pointer to be able to access the display component directly in c++ + //TWeakObjectPtr DisplayComponentReference; + + // Lerping functions and events + void InitializeLerpToHand(FBPActorGripInformation& GripInfo); + void HandleGlobalLerpToHand(FBPActorGripInformation& GripInformation, FTransform& WorldTransform, float DeltaTime); + + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void CancelGlobalLerpToHand(uint8 GripID); + + //UPROPERTY(BlueprintAssignable, Category = "Grip Events") + // FVROnControllerGripSignature OnLerpToHandBegin; + + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnLerpToHandFinished; + + + // If true we will scale the tracking of the motion controller by the TrackingScaler value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bScaleTracking; + + // A scale to be applied to the tracked positions of the controller if bScaleTracking is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bScaleTracking")) + FVector TrackingScaler; + + // If true we will use the minimum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLimitMinHeight; + + // The minimum height to allow for this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.0", UIMin = "0.0", EditCondition = "bLimitMinHeight")) + float MinimumHeight; + + // If true we will use the maximum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLimitMaxHeight; + + // The maximum height to allow for this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitMinHeight")) + float MaximumHeight; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + //bool bOffsetByHMD; + + // If true this controller will attempt to stay within its LeashRange distance from the HMD + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLeashToHMD; + + // How far away from the HMD the controller should stay max (vector distance) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLeashToHMD")) + float LeashRange; + + void ApplyTrackingParameters(FVector& OriginalPosition, bool bIsInGameThread, bool bApplyZeroing = true); + bool HasTrackingParameters(); + + // When true any physics constraints will be attached to the grip pivot instead of a new kinematic actor in the scene + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced") + bool bConstrainToPivot; + + UPROPERTY() + TObjectPtr AttachChar; + void UpdateTracking(float DeltaTime); + virtual void OnAttachmentChanged() override; + + FVector LastLocationForLateUpdate; + FTransform LastRelativePosition; + + // If true will smooth the hand tracking data with a TInterp function + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + bool bSmoothHandTracking; + + bool bWasSmoothingHand; + + // If true will smooth hand tracking with the Linear and Rotational 1 Euro low pass settings instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + bool bSmoothWithEuroLowPassFunction; + + // The interp speed to use if smoothing is enabled and not using the 1 Euro smoothing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + float SmoothingSpeed; + + // Smoothing parameters when using the 1 Euro low pass option + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + FBPEuroLowPassFilterTrans EuroSmoothingParams; + + FTransform LastSmoothRelativeTransform; + + // Type of velocity calculation to use for the motion controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + EVRVelocityType VelocityCalculationType; + + // If we should sample the velocity in world or local space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + bool bSampleVelocityInWorldSpace; + + // If not using velocity mode "default" this is the number of sample to keep + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + int32 VelocitySamples; + + FBPLowPassPeakFilter PeakFilter; + + virtual FVector GetComponentVelocity() const override; + + // If true will offset the tracked location of the controller by the controller profile that is currently loaded. + // Thows the event OnControllerProfileTransformChanged when it happens so that you can adjust specific components + // Like procedural ones for the offset (procedural meshes are already correctly offset for the controller and + // need rewound. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bOffsetByControllerProfile; + + // Stores current transform so we don't have to keep casting + FTransform CurrentControllerProfileTransform; + + // Called when the controller profile changed and we have a new transform (only if bOffsetByControllerProfile is true) + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnProfileTransformChanged OnControllerProfileTransformChanged; + + // Called when the controller profile changed and we have a new transform (only if bOffsetByControllerProfile is true) + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnGripOutOfRange OnGripOutOfRange; + +private: + + GENERATED_BODY() + +public: + UGripMotionControllerComponent(const FObjectInitializer& ObjectInitializer); + + ~UGripMotionControllerComponent(); + + // Custom version of the component sweep function to remove that aggravating warning epic is throwing about skeletal mesh components. + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void InitializeComponent() override; + virtual void OnUnregister() override; + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + virtual void Deactivate() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void BeginDestroy() override; + virtual void BeginPlay() override; + + /** Post-physics tick function for this character */ + //UPROPERTY() + // FTickFunction PostPhysicsTickFunction; + +protected: + //~ Begin UActorComponent Interface. + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + virtual void SendRenderTransform_Concurrent() override; + //~ End UActorComponent Interface. + + void OnModularFeatureUnregistered(const FName& Type, class IModularFeature* ModularFeature); + + //IMotionController* GripPolledMotionController_GameThread; + //IMotionController* GripPolledMotionController_RenderThread; + //FCriticalSection GripPolledMotionControllerMutex; + + // Late update control variables (should likely struct these soon) + struct FRenderTrackingParams + { + FTransform GripRenderThreadRelativeTransform = FTransform::Identity; + FVector GripRenderThreadComponentScale = FVector::ZeroVector; + FTransform GripRenderThreadProfileTransform = FTransform::Identity; + FVector GripRenderThreadLastLocationForLateUpdate = FVector::ZeroVector; + + // Smoothing info + bool bRenderSmoothHandTracking = false; + bool bRenderSmoothWithEuroLowPassFunction = false; + float RenderSmoothingSpeed = 0.0f; + FBPEuroLowPassFilterTrans RenderEuroSmoothingParams; + FTransform RenderLastSmoothRelativeTransform = FTransform::Identity; + float RenderLastDeltaTime = 0.0f; + }LateUpdateParams; + + + FDelegateHandle NewControllerProfileEvent_Handle; + UFUNCTION() + void NewControllerProfileLoaded(); + void GetCurrentProfileTransform(bool bBindToNoticationDelegate); + +public: + + // Called when a controller first gets a valid tracked frame + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnTrackingEventSignature OnTrackingChanged; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnGrippedObject; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerDropSignature OnDroppedObject; + + // Called when a object is being socketed, prior to OnDrop being called and prior to the actual socketing being performed + // Generally an early entry to detach hands and handle pre-socketing logic + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerSocketSignature OnSocketingObject; + + // Called when a gripped object has been teleported + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerTeleportedGripsSignature OnTeleportedGrips; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnSecondaryGripRemoved; + + // Storage for remote players for secondary grips specifically + // Its more than a little hacky, but ordering of drop and the secondaries being + // removed on the rep notify require this in rare cases. If Epic ever fixes the last array state not containing removed + // grips then this would be a lot cleaner. + TArray SecondaryGripIDs; + + + // Called when an object we hold has its grip transform changed + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnGripTransformChanged; + + // Gets the hand enum + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "HandType", CompactNodeTitle = "HandType")) + void GetHandType(EControllerHand& Hand); + + // The component to use for basing the grip off of instead of the motion controller + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + TObjectPtr CustomPivotComponent; + + // The socket for the component to use for basing the grip off of instead of the motion controller + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + FName CustomPivotComponentSocketName; + + // If true then we will skip the pivot transform adjustment when gripping an object with the custom pivot + // This is here for legacy support for anyone not using "ConvertToControllerRelativeTransform". + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + bool bSkipPivotTransformAdjustment; + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintCallable, Category = "GripMotionController|CustomPivot") + void SetCustomPivotComponent(USceneComponent * NewCustomPivotComponent, FName PivotSocketName = NAME_None); + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "GetPivotTransform")) + FTransform GetPivotTransform_BP(); + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "GetPivotLocation")) + FVector GetPivotLocation_BP(); + + FORCEINLINE FTransform GetPivotTransform() + { + return IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketTransform(CustomPivotComponentSocketName) : this->GetComponentTransform(); + } + + FORCEINLINE FVector GetPivotLocation() + { + return IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketLocation(CustomPivotComponentSocketName) : this->GetComponentLocation(); + } + + // Increments with each grip, wraps back to 0 after max due to modulo operation + // I don't think that a 254 (126 total grips) grip index is going to be used up and allow duplicates unless + // someone does something crazy + uint8 GripIDIncrementer; + + + // INVALID_VRGRIP_ID is 0, so + 1 is 1 + inline uint8 GetNextGripID(bool bIsLocalGrip) + { + // We are skipping index 0 to leave it for INVALID_GRIP_ID index; + + if (!bIsLocalGrip) // We need to split them between 0-126 for gripped objects server side + { + if (GripIDIncrementer < 127) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer; + } + else // And 128 - 254 for local grips client side, with half for server initiated and half for client + { + + if (!IsServer()) + { + if (GripIDIncrementer < 63) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer + 128; + } + else + { + if (GripIDIncrementer < 63) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer + 128 + 64; + } + } + } + + // When possible I suggest that you use GetAllGrips/GetGrippedObjects instead of directly referencing this + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_GrippedObjects) + TArray GrippedObjects; + + // When possible I suggest that you use GetAllGrips/GetGrippedObjects instead of directly referencing this + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_LocallyGrippedObjects) + TArray LocallyGrippedObjects; + + // Local Grip TransactionalBuffer to store server sided grips that need to be emplaced into the local buffer + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_LocalTransaction) + TArray LocalTransactionBuffer; + + // Locally Gripped Array functions + + // Notify a client that their local grip was bad + UFUNCTION(Reliable, Client, Category = "GripMotionController") + void Client_NotifyInvalidLocalGrip(UObject * LocallyGrippedObject, uint8 GripID, bool bWasAGripConflict = false); + + // Notify the server that we locally gripped something + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyLocalGripAddedOrChanged(const FBPActorGripInformation & newGrip); + + // Notify the server that we changed some secondary attachment information + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifySecondaryAttachmentChanged( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo); + + // Notify the server that we changed some secondary attachment information + // This one specifically sends out the new relative location for a retain secondary grip + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifySecondaryAttachmentChanged_Retain( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform); + + // Notify change on relative position editing as well, make RPCS callable in blueprint + // Notify the server that we locally gripped something + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifyLocalGripRemoved(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 OptAngularVelocity, FVector_NetQuantize100 OptLinearVelocity); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ClientAuth") + EVRClientAuthConflictResolutionMode ClientAuthConflictResolutionMethod; + + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnClientAuthGripConflict OnClientAuthGripConflict; + + // Handle a server initiated grip + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyHandledTransaction(uint8 GripID); + + // Enable this to send the TickGrip event every tick even for non custom grip types - has a slight performance hit + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bAlwaysSendTickGrip; + + // Clean up a grip that is "bad", object is being destroyed or was a bad destructible mesh + void CleanUpBadGrip(TArray &GrippedObjectsArray, int GripIndex, bool bReplicatedArray); + void CleanUpBadPhysicsHandles(); + + // Recreates a grip physics handle bodies + // If FullRecreate is false then it will only set the COM and actors, otherwise will re-init the entire grip + bool UpdatePhysicsHandle(uint8 GripID, bool bFullyRecreate = true); + bool UpdatePhysicsHandle(const FBPActorGripInformation & GripInfo, bool bFullyRecreate = true); + + inline void NotifyGripTransformChanged(const FBPActorGripInformation & GripInfo) + { + if (OnGripTransformChanged.IsBound()) + { + FBPActorGripInformation CurrentGrip; + EBPVRResultSwitch Result; + GetGripByID(CurrentGrip, GripInfo.GripID, Result); + if (Result == EBPVRResultSwitch::OnSucceeded) + { + OnGripTransformChanged.Broadcast(CurrentGrip); + } + } + } + + // Recreates a grip in situations where the collision type or movement replication type may have been changed + inline void ReCreateGrip(FBPActorGripInformation & GripInfo) + { + int HandleIndex = 0; + if (GetPhysicsGripIndex(GripInfo, HandleIndex)) + { + DestroyPhysicsHandle(GripInfo); + } + + // Grip Type or replication was changed + NotifyGrip(GripInfo, true); + } + + // Handles variable state changes and specific actions on a grip replication + inline bool HandleGripReplication(FBPActorGripInformation & Grip, FBPActorGripInformation * OldGripInfo = nullptr) + { + if (Grip.ValueCache.bWasInitiallyRepped && Grip.GripID != Grip.ValueCache.CachedGripID) + { + // There appears to be a bug with TArray replication where if you replace an index with another value of that + // Index, it doesn't fully re-init the object, this is a workaround to re-zero non replicated variables + // when that happens. + Grip.ClearNonReppingItems(); + } + + // Ignore server down no rep grips, this is kind of unavoidable unless I make yet another list which I don't want to do + if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + // skip init + Grip.ValueCache.bWasInitiallyRepped = true; + + // null ptr so this doesn't block grip operations + Grip.GrippedObject = nullptr; + + // Set to paused so iteration skips it + Grip.bIsPaused = true; + } + + if (!Grip.ValueCache.bWasInitiallyRepped) // Hasn't already been initialized + { + Grip.ValueCache.bWasInitiallyRepped = NotifyGrip(Grip); // Grip it + + // Tick will keep checking from here on out locally + if (!Grip.ValueCache.bWasInitiallyRepped) + { + //UE_LOG(LogVRMotionController, Warning, TEXT("Replicated grip Notify grip failed, was grip called before the object was replicated to the client?")); + return false; + } + + if(Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + // Reset the secondary grip distance + Grip.SecondaryGripInfo.SecondaryGripDistance = 0.0f; + + if (FMath::IsNearlyZero(Grip.SecondaryGripInfo.LerpToRate)) // Zero, could use IsNearlyZero instead + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + else + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + } + + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + SecondaryGripIDs.Add(Grip.GripID); + IVRGripInterface::Execute_OnSecondaryGrip(Grip.GrippedObject, this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + OnSecondaryGripAdded.Broadcast(Grip); + } + //Grip.ValueCache.bWasInitiallyRepped = true; // Set has been initialized + } + else if(OldGripInfo != nullptr) // Check for changes from cached information if we aren't skipping the delta check + { + // Manage lerp states + if ((OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment != Grip.SecondaryGripInfo.bHasSecondaryAttachment) || + (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment) || + (!OldGripInfo->SecondaryGripInfo.SecondaryRelativeTransform.Equals(Grip.SecondaryGripInfo.SecondaryRelativeTransform))) + { + // Reset the secondary grip distance + Grip.SecondaryGripInfo.SecondaryGripDistance = 0.0f; + + if (FMath::IsNearlyZero(Grip.SecondaryGripInfo.LerpToRate)) // Zero, could use IsNearlyZero instead + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + else + { + // New lerp + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + } + else // Post Lerp + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::EndLerp; + } + } + + bool bSendReleaseEvent = ((!Grip.SecondaryGripInfo.bHasSecondaryAttachment && OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment) || + ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment) && + (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment))); + + bool bSendGripEvent = (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + (!OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment || (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment))); + + if (bSendReleaseEvent) + { + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(Grip.GrippedObject, this, OldGripInfo->SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGripRelease(this, OldGripInfo->SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + SecondaryGripIDs.Remove(Grip.GripID); + OnSecondaryGripRemoved.Broadcast(Grip); + } + + if (bSendGripEvent) + { + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + SecondaryGripIDs.Add(Grip.GripID); + IVRGripInterface::Execute_OnSecondaryGrip(Grip.GrippedObject, this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + OnSecondaryGripAdded.Broadcast(Grip); + } + } + + if (OldGripInfo->GripCollisionType != Grip.GripCollisionType || + OldGripInfo->GripMovementReplicationSetting != Grip.GripMovementReplicationSetting || + OldGripInfo->GrippedBoneName != Grip.GrippedBoneName || + OldGripInfo->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings != Grip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings + ) + { + ReCreateGrip(Grip); // Need to re-create grip + } + else // If re-creating the grip anyway we don't need to do the below + { + bool bTransformChanged = !OldGripInfo->RelativeTransform.Equals(Grip.RelativeTransform); + + // If physics settings got changed server side + if (!FMath::IsNearlyEqual(OldGripInfo->Stiffness, Grip.Stiffness) || + !FMath::IsNearlyEqual(OldGripInfo->Damping, Grip.Damping) || + OldGripInfo->AdvancedGripSettings.PhysicsSettings != Grip.AdvancedGripSettings.PhysicsSettings || + bTransformChanged + ) + { + UpdatePhysicsHandle(Grip); + + if (bTransformChanged) + { + NotifyGripTransformChanged(Grip); + } + } + } + } + + // Set caches now for next rep + Grip.ValueCache.CachedGripID = Grip.GripID; + + return true; + } + + inline void CheckTransactionBuffer() + { + if (LocalTransactionBuffer.Num()) + { + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; --i) + { + if (LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped && LocalTransactionBuffer[i].GripID != LocalTransactionBuffer[i].ValueCache.CachedGripID) + { + // There appears to be a bug with TArray replication where if you replace an index with another value of that + // Index, it doesn't fully re-init the object, this is a workaround to re-zero non replicated variables + // when that happens. + LocalTransactionBuffer[i].ClearNonReppingItems(); + } + + if (!LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped && LocalTransactionBuffer[i].GrippedObject->IsValidLowLevelFast()) + { + LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped = true; + LocalTransactionBuffer[i].ValueCache.CachedGripID = LocalTransactionBuffer[i].GripID; + + int32 Index = LocallyGrippedObjects.Add(LocalTransactionBuffer[i]); + + if (Index != INDEX_NONE) + { + NotifyGrip(LocallyGrippedObjects[Index]); + } + + Server_NotifyHandledTransaction(LocalTransactionBuffer[i].GripID); + } + } + } + } + + UFUNCTION() + virtual void OnRep_LocalTransaction(TArray OriginalArrayState) // Original array state is useless without full serialize, it just hold last delta + { + CheckTransactionBuffer(); + } + + UFUNCTION() + virtual void OnRep_GrippedObjects(TArray OriginalArrayState) // Original array state is useless without full serialize, it just hold last delta + { + // Need to think about how best to handle the simulating flag here, don't handle for now + // Check for removed gripped actors + // This might actually be better left as an RPC multicast + + for (int i = GrippedObjects.Num() - 1; i >= 0; --i) + { + HandleGripReplication(GrippedObjects[i], OriginalArrayState.FindByKey(GrippedObjects[i].GripID)); + } + } + + UFUNCTION() + virtual void OnRep_LocallyGrippedObjects(TArray OriginalArrayState) + { + for (int i = LocallyGrippedObjects.Num() - 1; i >= 0; --i) + { + HandleGripReplication(LocallyGrippedObjects[i], OriginalArrayState.FindByKey(LocallyGrippedObjects[i].GripID)); + } + } + + UPROPERTY(BlueprintReadWrite, Category = "GripMotionController") + TArray AdditionalLateUpdateComponents; + + // Movement Replication + // Actor needs to be replicated for this to work + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedControllerTransform, Category = "GripMotionController|Networking") + FBPVRComponentPosRep ReplicatedControllerTransform; + + FVector LastUpdatesRelativePosition; + FRotator LastUpdatesRelativeRotation; + + bool bLerpingPosition; + bool bReppedOnce; + + UFUNCTION() + virtual void OnRep_ReplicatedControllerTransform(); + + + // Run the smoothing step + void RunNetworkedSmoothing(float DeltaTime); + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + // On dedicated servers the update rate should be at or lower than the server tick rate for smoothing to work + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking", meta = (ClampMin = "0", UIMin = "0")) + float ControllerNetUpdateRate; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float ControllerNetUpdateCount; + + // Whether to smooth (lerp) between ticks for the replicated motion, DOES NOTHING if update rate is larger than FPS! + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking") + bool bSmoothReplicatedMotion; + + // If true then we will use exponential smoothing with buffered correction + UPROPERTY(EditAnywhere, Category = "GripMotionController|Networking|Smoothing", meta = (editcondition = "bSmoothReplicatedMotion")) + bool bUseExponentialSmoothing = true; + + // Timestep of smoothing translation + UPROPERTY(EditAnywhere, Category = "GripMotionController|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float InterpolationSpeed = 25.0f; + + // Max distance to allow smoothing before snapping the remainder + UPROPERTY(EditAnywhere, Category = "GripMotionController|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float NetworkMaxSmoothUpdateDistance = 50.f; + + // Max distance to allow smoothing before snapping entirely to the new position + UPROPERTY(EditAnywhere, Category = "GripMotionController|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float NetworkNoSmoothUpdateDistance = 100.f; + + // Whether to replicate even if no tracking (FPS or test characters) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking") + bool bReplicateWithoutTracking; + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendControllerTransform(FBPVRComponentPosRep NewTransform); + + // Pointer to an override to call from the owning character - this saves 7 bits a rep avoiding component IDs on the RPC + typedef void (AVRBaseCharacter::*VRBaseCharTransformRPC_Pointer)(FBPVRComponentPosRep NewTransform); + VRBaseCharTransformRPC_Pointer OverrideSendTransform; + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { + // I like epics new authority check more than mine + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner && MyOwner->Role == ENetRole::ROLE_Authority); + } + + // Shorthand for checking if we are in seamless travel + inline bool IsTravelingOrNullWorld() const + { + UWorld* myWorld = GetWorld(); + if (IsValid(myWorld)) + { + return myWorld->IsInSeamlessTravel(); + } + + // We don't have a valid world we are part of, don't do anything. + return true; + } + + // Returns if this is the owning connection for the motion controller + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "IsLocallyControlled")) + bool BP_IsLocallyControlled(); + + // Checks if the controllers own is torn off on the network, used to skip some RPCS + inline bool IsTornOff() const + { + const AActor* MyOwner = GetOwner(); + return MyOwner ? MyOwner->GetTearOff() : false; + } + + + inline bool IsServer() const + { + if (GEngine != nullptr && GWorld != nullptr) + { + return GEngine->GetNetMode(GWorld) < NM_Client; + } + + return false; + } + + // Drops a gripped object and sockets it to the given component at the given relative transform. + // *Note*: If both the parent and the child are simulating it also delays a single tick and then re-applies the relative transform. + // This is to avoid a race condition where we need to wait for the next physics update. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropAndSocketObject(const FTransform_NetQuantize & RelativeTransformToParent, UObject * ObjectToDrop = nullptr, uint8 GripIDToDrop = 0, USceneComponent * SocketingParent = nullptr, FName OptionalSocketName = NAME_None, bool bWeldBodies = true); + + // Drops a grip and sockets it to the given component at the given relative transform. + // *Note*: If both the parent and the child are simulating it also delays a single tick and then re-applies the relative transform. + // This is to avoid a race condition where we need to wait for the next physics update. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropAndSocketGrip(const FBPActorGripInformation &GripToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + bool DropAndSocketGrip_Implementation(const FBPActorGripInformation& GripToDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies = true, bool bSkipServerNotify = false); + + // Notify the server about a new drop and socket + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyDropAndSocketGrip(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + + UFUNCTION(Reliable, NetMulticast) + void NotifyDropAndSocket(const FBPActorGripInformation &NewDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies = true); + + void DropAndSocket_Implementation(const FBPActorGripInformation &NewDrop); + void Socket_Implementation(UObject * ObjectToSocket, bool bWasSimulating, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + + // Keep a hard reference to the drop to avoid deletion errors + UPROPERTY() + TArray> ObjectsWaitingForSocketUpdate; + + // Resets the transform of a socketed grip 1 tick later, this is to avoid a race condition with simulating grips. + // Their constraint can change the transform before or after the attachment happens if the parent and the child are both simulating. + UFUNCTION() + void SetSocketTransform(UObject* ObjectToSocket, /*USceneComponent * SocketingParent,*/ const FTransform_NetQuantize RelativeTransformToParent/*, FName OptionalSocketName, bool bWeldBodies*/); + + /* Auto grip any uobject that is/root is a primitive component and has the VR Grip Interface + these are stored in a Tarray that will prevent destruction of the object, you MUST ungrip an actor if you want to kill it + The WorldOffset is the transform that it will remain away from the controller, if you use the world position of the actor then it will grab + at the point of intersection. + + If WorldOffsetIsRelative is true then it will not convert the transform from world space but will instead use that offset directly. + You could pass in a socket relative transform with this set for snapping or an empty transform to snap the object at its 0,0,0 point. + + If you declare a valid OptionSnapToSocketName then it will instead snap the actor to the relative offset + location that the socket is to its parent actor. + + It will only do this if the WorldOffset value is left default, if it is not, then it will treat this as the name of the slot + that you already have the transform for. + + If you declare a valid OptionalBoneToGripName then it will grip that physics body with physics grips (It will expect a bone worldspace transform then, + if you pass in the normal actor/root component world space transform then the grip will not be positioned correctly). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripObject( + UObject * ObjectToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative = false, + FName OptionalSnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 2250.0f, + float GripDamping = 140.0f, bool bIsSlotGrip = false); + + + // Auto drop any uobject that is/root is a primitive component and has the VR Grip Interface + // If an object is passed in it will attempt to drop it, otherwise it will attempt to find and drop the given grip id + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropObject( + UObject* ObjectToDrop = nullptr, + uint8 GripIdToDrop = 0, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector); + + // Auto grip any uobject that is/root is a primitive component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripObjectByInterface(UObject* ObjectToGrip, const FTransform &WorldOffset, bool bWorldOffsetIsRelative = false, FName OptionalBoneToGripName = NAME_None, FName OptionalSnapToSocketName = NAME_None, bool bIsSlotGrip = false); + + // Auto drop any uobject that is/root is a primitive component and has the VR Grip Interface + // If an object is passed in it will attempt to drop it, otherwise it will attempt to find and drop the given grip id + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropObjectByInterface(UObject* ObjectToDrop = nullptr, uint8 GripIDToDrop = 0, FVector OptionalAngularVelocity = FVector::ZeroVector, FVector OptionalLinearVelocity = FVector::ZeroVector); + + bool DropObjectByInterface_Implementation(UObject* ObjectToDrop = nullptr, uint8 GripIDToDrop = 0, FVector OptionalAngularVelocity = FVector::ZeroVector, FVector OptionalLinearVelocity = FVector::ZeroVector, bool bSkipNotify = false); + + /* Grip an actor, these are stored in a Tarray that will prevent destruction of the object, you MUST ungrip an actor if you want to kill it + The WorldOffset is the transform that it will remain away from the controller, if you use the world position of the actor then it will grab + at the point of intersection. + + If WorldOffsetIsRelative is true then it will not convert the transform from world space but will instead use that offset directly. + You could pass in a socket relative transform with this set for snapping or an empty transform to snap the object at its 0,0,0 point. + + If you declare a valid OptionSnapToSocketName then it will instead snap the actor to the relative offset + location that the socket is to its parent actor. + + It will only do this if the WorldOffset value is left default, if it is not, then it will treat this as the name of the slot + that you already have the transform for. + + If you declare a valid OptionalBoneToGripName then it will grip that physics body with physics grips (It will expect a bone worldspace transform then, + if you pass in the normal actor/root component world space transform then the grip will not be positioned correctly). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripActor( + AActor* ActorToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative = false, + FName OptionalSnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 1500.0f, + float GripDamping = 200.0f, + bool bIsSlotGrip = false); + + // Drop a gripped actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropActor( + AActor* ActorToDrop, + bool bSimulate, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector + ); + + // Grip a component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripComponent( + UPrimitiveComponent* ComponentToGrip, + const FTransform &WorldOffset, bool bWorldOffsetIsRelative = false, + FName OptionalsnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 1500.0f, + float GripDamping = 200.0f, + bool bIsSlotGrip = false); + + // Drop a gripped component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropComponent( + UPrimitiveComponent* ComponentToDrop, + bool bSimulate, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector + ); + + // Master function for dropping a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropGrip( + const FBPActorGripInformation &Grip, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector); + + bool DropGrip_Implementation( + const FBPActorGripInformation& Grip, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector, + bool bSkipNotify = false); + + // No Longer replicated, called via on rep now instead. + //UFUNCTION(Reliable, NetMulticast) + bool NotifyGrip(FBPActorGripInformation &NewGrip, bool bIsReInit = false); + + UFUNCTION(Reliable, NetMulticast) + void NotifyDrop(const FBPActorGripInformation &NewDrop, bool bSimulate); + + // Used so drop logic can be filtered + void Drop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate); + + // Get a grip by actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByActor(FBPActorGripInformation &Grip, AActor * ActorToLookForGrip, EBPVRResultSwitch &Result); + + // Get a grip by component + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByComponent(FBPActorGripInformation &Grip, UPrimitiveComponent * ComponentToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by object, will auto use ByComponent or ByActor + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByObject(FBPActorGripInformation &Grip, UObject * ObjectToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by its grip ID *NOTE*: Grip IDs are only unique to their controller, do NOT use them as cross controller identifiers + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByID(FBPActorGripInformation &Grip, uint8 IDToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by its grip ID *NOTE*: Grip IDs are only unique to their controller, do NOT use them as cross controller identifiers + FBPActorGripInformation * GetGripPtrByID(uint8 IDToLookForGrip); + + // Get the physics velocities of a grip + UFUNCTION(BlueprintPure, Category = "GripMotionController") + void GetPhysicsVelocities(const FBPActorGripInformation &Grip, FVector &CurAngularVelocity, FVector &CurLinearVelocity); + + // Get the physics constraint force of a simulating grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GetPhysicsConstraintForce(const FBPActorGripInformation& Grip, FVector& AngularForce, FVector& LinearForce); + + // Get the root components mass of a grip + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static void GetGripMass(const FBPActorGripInformation& Grip, float& Mass); + + /* Returns the world transform of a gripped object from the grip structure */ + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static FTransform GetGrippedObjectTransform(const FBPActorGripInformation& Grip); + + // Sets whether an active grip is paused or not (is not replicated by default as it is likely you will want to pass variables with this setting). + // If you want it server authed you should RPC a bool down with any additional information (ie: attach location). + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripPaused( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + bool bIsPaused = false, + bool bNoConstraintWhenPaused = false + ); + + // Sets whether an active hybrid grip is locked to its soft setting (is not replicated by default as it is likely you will want to pass variables with this setting). + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripHybridLock( + const FBPActorGripInformation& Grip, + EBPVRResultSwitch& Result, + bool bIsLocked = false + ); + + // Sets the transform to stay at during pause + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void SetPausedTransform( + const FBPActorGripInformation &Grip, + const FTransform & PausedTransform, + bool bTeleport = false + ); + + // Set the Grip Collision Type of a grip, call server side if not a local grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripCollisionType( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + EGripCollisionType NewGripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics + ); + + + // Set the late update setting of a grip, call server side if not a local grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripLateUpdateSetting( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + EGripLateUpdateSettings NewGripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping + ); + + // Set the relative transform of a grip, call server side if not a local grip + // Can check HasGripAuthority to decide if callable locally + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripRelativeTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewRelativeTransform + ); + + // Set the addition transform of a grip, CALL LOCALLY, not server side, Addition transform is not replicated + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripAdditionTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewAdditionTransform, bool bMakeGripRelative = false + ); + + // Set the constraint stiffness and dampening of a grip, call server side if not a local grip + // Can check HasGripAuthority to decide if callable locally + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripStiffnessAndDamping( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + float NewStiffness, float NewDamping, bool bAlsoSetAngularValues = false, float OptionalAngularStiffness = 0.0f, float OptionalAngularDamping = 0.0f + ); + + // Used to convert an offset transform to grip relative, useful for storing an initial offset and then lerping back to 0 without re-calculating every tick + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "CreateGripRelativeAdditionTransform")) + FTransform CreateGripRelativeAdditionTransform_BP( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative = false + ); + + inline FTransform CreateGripRelativeAdditionTransform( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative = false + ); + + + // Checks if we have grip authority + inline bool HasGripAuthority(const FBPActorGripInformation &Grip); + + // Checks if we have grip authority over a given object + inline bool HasGripAuthority(const UObject * ObjToCheck); + + // Returns if we have grip authority (can call drop / grip on this grip) + // Mostly for networked games as local grips are client authed and all others are server authed + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripAuthority")) + bool BP_HasGripAuthority(const FBPActorGripInformation & Grip); + + // Returns if we have grip authority (can call drop / grip on this grip) + // Mostly for networked games as local grips are client authed and all others are server authed + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripAuthorityForObject")) + bool BP_HasGripAuthorityForObject(const UObject* ObjToCheck); + + // Checks if we should be handling the movement of a grip based on settings for it + inline bool HasGripMovementAuthority(const FBPActorGripInformation & Grip); + + // Returns if we have grip movement authority (we handle movement of the grip) + // Mostly for networked games where ClientSide will be true for all and ServerSide will be true for server only + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripMovementAuthority")) + bool BP_HasGripMovementAuthority(const FBPActorGripInformation & Grip); + + // Running the gripping logic in its own function as the main tick was getting bloated + void TickGrip(float DeltaTime); + + // Splitting logic into separate function + void HandleGripArray(TArray &GrippedObjectsArray, const FTransform & ParentTransform, float DeltaTime, bool bReplicatedArray = false); + + // Gets the world transform of a grip, modified by secondary grips, returns if it has a valid transform, if not then this tick will be skipped for the object + bool GetGripWorldTransform(TArray& GripScripts, float DeltaTime,FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport, bool &bForceADrop); + + // Calculate component to world without the protected tag, doesn't set it, just returns it + inline FTransform CalcControllerComponentToWorld(FRotator Orientation, FVector Position) + { + return this->CalcNewComponentToWorld(FTransform(Orientation, Position)); + } + + // Converts a worldspace transform into being relative to this motion controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + FTransform ConvertToControllerRelativeTransform(const FTransform & InTransform); + + // Creates a secondary grip relative transform + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static FTransform ConvertToGripRelativeTransform(const FTransform& GrippedActorTransform, const FTransform & InTransform); + + // Gets if the given object is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsObjectHeld(const UObject * ObjectToCheck); + + // Gets if the given actor is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsHeld(const AActor * ActorToCheck); + + // Gets if the given component is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsComponentHeld(const UPrimitiveComponent * ComponentToCheck); + + // Gets if the given Component is a secondary attach point to a gripped actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GetIsSecondaryAttachment(const USceneComponent * ComponentToCheck, FBPActorGripInformation & Grip); + + // Get if we have gripped objects, local or replicated + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool HasGrippedObjects(); + + // Get the first active and valid grip (local and remote auth both, priority remote) + // Returns false is there is none + UFUNCTION(BlueprintCallable, meta = (Keywords = "Grip", DisplayName = "GetFirstActiveGrip", ScriptName = "GetFirstActiveGrip"), Category = "GripMotionController") + bool K2_GetFirstActiveGrip(FBPActorGripInformation& FirstActiveGrip); + + // Get the first active and valid grip (local and remote auth both, priority remote) + // Returns nullptr if there is none + FBPActorGripInformation* GetFirstActiveGrip(); + + // Get list of all gripped objects grip info structures (local and normal both) + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetAllGrips(TArray &GripArray); + + // Get list of all gripped actors + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedActors(TArray &GrippedActorArray); + + // Get list of all gripped objects + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedObjects(TArray &GrippedObjectsArray); + + // Get list of all gripped components + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedComponents(TArray &GrippedComponentsArray); + + // After teleporting a pawn you NEED to call this, otherwise gripped objects will travel with a sweeped move and can get caught on geometry + // The base Teleport() function automatically calls this already, but when you manually set location you should do it yourself. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void PostTeleportMoveGrippedObjects(); + + bool bIsPostTeleport; + + // Move a single gripped item back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrippedActor(AActor * GrippedActorToMove, bool bTeleportPhysicsGrips = true); + + // Move a single gripped item back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrippedComponent(UPrimitiveComponent * ComponentToMove, bool bTeleportPhysicsGrips = true); + + // Move a single grip back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + // bIsForPostTeleport says whether we shuld allow the DropOnTeleport logic to apply or not + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrip(UPARAM(ref)FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips = true, bool bIsForPostTeleport = false); + bool TeleportMoveGrip_Impl(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport, FTransform & OptionalTransform); + + // Moves all grips back into position immediately + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void TeleportMoveGrips(bool bTeleportPhysicsGrips = true, bool bIsForPostTeleport = false); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentPoint(UObject * GrippedObjectToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentToGrip(const FBPActorGripInformation & GripToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentToGripByID(const uint8 GripID, USceneComponent* SecondaryPointComponent, const FTransform& OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentPoint(UObject * GrippedObjectToRemoveAttachment, float LerpToTime = 0.25f); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentFromGrip(const FBPActorGripInformation & GripToRemoveAttachment, float LerpToTime = 0.25f); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentFromGripByID(const uint8 GripID = 0, float LerpToTime = 0.25f); + + // If this is true the controller will always attempt to get the current tracking information, regardless of it TrackingStatus is Untracked or not + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bIgnoreTrackingStatus; + + // This is for testing, setting it to true allows you to test grip with a non VR enabled pawn + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bUseWithoutTracking; + + bool CheckComponentWithSweep(UPrimitiveComponent * ComponentToCheck, FVector Move, FRotator newOrientation, bool bSkipSimulatingComponents/*, bool & bHadBlockingHitOut*/); + + // For physics handle operations + void OnGripMassUpdated(FBodyInstance* GripBodyInstance); + bool SetUpPhysicsHandle(const FBPActorGripInformation &NewGrip, TArray * GripScripts = nullptr); + bool DestroyPhysicsHandle(const FBPActorGripInformation &Grip, bool bSkipUnregistering = false); + void UpdatePhysicsHandleTransform(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform); + bool SetGripConstraintStiffnessAndDamping(const FBPActorGripInformation *Grip, bool bUseHybridMultiplier = false); + bool GetPhysicsJointLength(const FBPActorGripInformation &GrippedActor, UPrimitiveComponent * rootComp, FVector & LocOut); + + TArray PhysicsGrips; + FBPActorPhysicsHandleInformation * GetPhysicsGrip(const FBPActorGripInformation & GripInfo); + FBPActorPhysicsHandleInformation * GetPhysicsGrip(const uint8 GripID); + bool GetPhysicsGripIndex(const FBPActorGripInformation & GripInfo, int & index); + FBPActorPhysicsHandleInformation * CreatePhysicsGrip(const FBPActorGripInformation & GripInfo); + bool DestroyPhysicsHandle(FBPActorPhysicsHandleInformation * HandleInfo); + bool PausePhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo); + bool UnPausePhysicsHandle(FBPActorGripInformation& GripInfo, FBPActorPhysicsHandleInformation* HandleInfo); + + // Gets the advanced physics handle settings + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "GetPhysicsHandleSettings")) + bool GetPhysicsHandleSettings(UPARAM(ref)const FBPActorGripInformation & Grip, FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsOut); + + // Sets the advanced physics handle settings, also automatically updates it + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "SetPhysicsHandleSettings")) + bool SetPhysicsHandleSettings(UPARAM(ref)const FBPActorGripInformation& Grip, UPARAM(ref) const FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsIn); + + // Creates a physics handle for this grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "SetUpPhysicsHandle")) + bool SetUpPhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation &Grip); + + // Destroys a physics handle for this grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "DestroyPhysicsHandle")) + bool DestroyPhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation &Grip); + + // Re-creates a physics handle for this grip + // If bFullyRecreate is true then it will set all of the handle properties, if not then it will only reset the physics actors and COM positions + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "UpdatePhysicsHandle")) + bool UpdatePhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation& Grip, bool bFullyRecreate = true); + + // Update the location of the physics handle + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "UpdatePhysicsHandleTransform")) + void UpdatePhysicsHandleTransform_BP(UPARAM(ref)const FBPActorGripInformation &GrippedActor, UPARAM(ref)const FTransform& NewTransform); + + // Get the grip distance of either the physics handle if there is one, or the difference from the hand to the root component if there isn't + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "GetGripDistance")) + bool GetGripDistance_BP(UPARAM(ref)FBPActorGripInformation &Grip, FVector ExpectedLocation, float & CurrentDistance); + + /** If true, the Position and Orientation args will contain the most recent controller state */ + virtual bool GripPollControllerState(FVector& Position, FRotator& Orientation, float WorldToMetersScale); + bool GripPollControllerState_GameThread(FVector& Position, FRotator& Orientation, bool& OutbProvidedLinearVelocity, FVector& OutLinearVelocity, bool& OutbProvidedAngularVelocity, FVector& OutAngularVelocityAsAxisAndLength, bool& OutbProvidedLinearAcceleration, FVector& OutLinearAcceleration, float WorldToMetersScale); + bool GripPollControllerState_RenderThread(FVector& Position, FRotator& Orientation, float WorldToMetersScale); + + /** Whether or not this component had a valid tracked controller associated with it this frame*/ + //bool bTracked; + + /** Whether or not this component had a valid tracked device this frame + * + * Use this instead of the normal IsTracked() for the motion controller which will not return the correct information. + * This is messy but I have no access to the various private members of the motion controller. + */ + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GripControllerIsTracked() const; + + /** Returns the first valid device ID for this motion controller (across enabled XR systems) + * + * If bCheckOpenVROnly is true, then it only checks for OpenVR IDs (for use with my openVR nodes). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetControllerDeviceID(FXRDeviceId & DeviceID, EBPVRResultSwitch &Result, bool bCheckOpenVROnly = false); + + // Return whether this controller has authority (is locally net owned) + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool HasAuthority() const; + + /** Whether or not this component has authority within the frame*/ + //bool bHasAuthority; + + /** Whether or not this component has informed the visualization component (if present) to start rendering */ + //bool bHasStartedRendering = false; + +private: + /** Whether or not this component is currently on the network server*/ + //bool bIsServer; + + /** View extension object that can persist on the render thread without the motion controller component */ + class FGripViewExtension : public FSceneViewExtensionBase + { + + // #TODO: 4.18 - Uses an auto register base now, revise declaration and implementation + public: + FGripViewExtension(const FAutoRegister& AutoRegister, UGripMotionControllerComponent* InMotionControllerComponent); + + virtual ~FGripViewExtension() {} + + /** ISceneViewExtension interface */ + virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {} + virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {} + virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override; + //virtual void PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) override {} + //virtual void PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override; + virtual void PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView) override {} + virtual void PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) override; + + virtual int32 GetPriority() const override { return -10; } + + private: + friend class UGripMotionControllerComponent; + + /** Motion controller component associated with this view extension */ + UGripMotionControllerComponent* MotionControllerComponent; + + FExpandedLateUpdateManager LateUpdate; + }; + TSharedPtr< FGripViewExtension, ESPMode::ThreadSafe > GripViewExtension; + +}; + +FTransform inline UGripMotionControllerComponent::CreateGripRelativeAdditionTransform( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative +) +{ + + FTransform FinalTransform; + + if (bGripRelative) + { + FinalTransform = FTransform(AdditionTransform.GetRotation(), GripToSample.RelativeTransform.GetRotation().RotateVector(AdditionTransform.GetLocation()), AdditionTransform.GetScale3D()); + } + else + { + const FTransform PivotToWorld = FTransform(FQuat::Identity, GripToSample.RelativeTransform.GetLocation()); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -GripToSample.RelativeTransform.GetLocation()); + + // Create a transform from it + FTransform RotationOffsetTransform(AdditionTransform.GetRotation(), FVector::ZeroVector); + FinalTransform = FTransform(FQuat::Identity, AdditionTransform.GetLocation(), AdditionTransform.GetScale3D()) * WorldToPivot * RotationOffsetTransform * PivotToWorld; + } + + return FinalTransform; +} + +bool inline UGripMotionControllerComponent::HasGripAuthority(const FBPActorGripInformation &Grip) +{ + if (((Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive && + Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && IsServer()) || + ((Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && bHasAuthority)) + { + return true; + } + + return false; +} + +bool inline UGripMotionControllerComponent::HasGripAuthority(const UObject * ObjToCheck) +{ + if (!ObjToCheck) + return false; + + // If it isn't interfaced and we are the server, then allow gripping it + if (!ObjToCheck->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()) && IsServer()) + return true; + + // I know that it is bad practice to const_cast here, but I want the object to be passed in const + EGripMovementReplicationSettings MovementRepType = IVRGripInterface::Execute_GripMovementReplicationType(const_cast(ObjToCheck)); + + if (((MovementRepType != EGripMovementReplicationSettings::ClientSide_Authoritive && + MovementRepType != EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && IsServer()) || + ((MovementRepType == EGripMovementReplicationSettings::ClientSide_Authoritive || + MovementRepType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && bHasAuthority)) + { + return true; + } + + return false; +} + +bool inline UGripMotionControllerComponent::HasGripMovementAuthority(const FBPActorGripInformation &Grip) +{ + if (IsServer()) + { + return true; + } + else + { + if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceClientSideMovement || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return true; + } + else if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement) + { + return false; + } + + // Use original movement type is overridden when initializing the grip and shouldn't happen + check(Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::KeepOriginalMovement); + } + + return false; +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h new file mode 100644 index 0000000..376ede0 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "GS_Default.generated.h" + + +/** +* The default grip transform logic for the motion controllers +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGS_Default : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_Default(const FObjectInitializer& ObjectInitializer); + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + virtual void GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform); + virtual void ApplySmoothingAndLerp(FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, float DeltaTime); + + virtual void CalculateSecondaryLocation(FVector & frontLoc, const FVector& BasePoint, FBPActorGripInformation& Grip, UGripMotionControllerComponent * GrippingController); +}; + +// An extended default grip script that adds less common grip features that were moved out of the default implementation +UCLASS(BlueprintType, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_ExtendedDefault : public UGS_Default +{ + GENERATED_BODY() +public: + + // Whether clamp the grip scaling in scaling grips + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings") + bool bLimitGripScaling; + + // Minimum size to allow scaling in double grip to reach + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bLimitGripScaling")) + FVector_NetQuantize100 MinimumGripScaling; + + // Maximum size to allow scaling in double grip to reach + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bLimitGripScaling")) + FVector_NetQuantize100 MaximumGripScaling; + + virtual void GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h new file mode 100644 index 0000000..19d8aee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h @@ -0,0 +1,318 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GripScripts/GS_Default.h" +#include "GS_GunTools.generated.h" + +class UGripMotionControllerComponent; + +// Event thrown when we enter into virtual stock mode +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRVirtualStockModeChangedSignature, bool, IsVirtualStockEngaged); + +// Global settings for this player +USTRUCT(BlueprintType, Category = "GunSettings") +struct VREXPANSIONPLUGIN_API FBPVirtualStockSettings +{ + GENERATED_BODY() +public: + + // *Global Value* Should we auto snap to the virtual stock by a set distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseDistanceBasedStockSnapping; + + // *Global Value* The distance before snapping to the stock / unsnapping + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + float StockSnapDistance; + + // *Global Value* The distance from the edge of the stock snap distance where it will be at 100% influence + // Prior to this threshold being hit it will lerp from standard hold to the virtual stock version. + // A value of 0.0f will leave it always off + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock", meta = (ClampMin = "0.00", UIMin = "0.00")) + float StockSnapLerpThreshold; + + // Current lerp value of the stock from zero influence to full influence + UPROPERTY(BlueprintReadOnly, Category = "VirtualStock") + float StockLerpValue; + + // *Global Value* An offset to apply to the HMD location to be considered the neck / mount pivot + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + FVector_NetQuantize100 StockSnapOffset; + + // *Global Value* If we want to have the stock location adjust to follow the primary hands Z value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bAdjustZOfStockToPrimaryHand; + + // *Global Value* Whether we should lerp the location of the rearmost (stock side) hand, mostly used for snipers. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock|Smoothing") + bool bSmoothStockHand; + + // *Global Value* How much influence the virtual stock smoothing should have, 0.0f is zero smoothing, 1.0f is full smoothing, you should test with full smoothing to get the amount you + // want and then set the smoothing value up until it feels right between the fully smoothed and unsmoothed values. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock|Smoothing", meta = (editcondition = "bSmoothStockHand", ClampMin = "0.00", UIMin = "0.00", ClampMax = "1.00", UIMax = "1.00")) + float SmoothingValueForStock; + + // Used to smooth filter the virtual stocks primary hand location + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings|VirtualStock|Smoothing") + FBPEuroLowPassFilterTrans StockHandSmoothing; + + // Draw debug elements showing the virtual stock location and angles to interacting components + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings|VirtualStock|Debug") + bool bDebugDrawVirtualStock; + + void CopyFrom(FBPVirtualStockSettings & B) + { + bUseDistanceBasedStockSnapping = B.bUseDistanceBasedStockSnapping; + StockSnapDistance = B.StockSnapDistance; + StockSnapLerpThreshold = B.StockSnapLerpThreshold; + StockSnapOffset = B.StockSnapOffset; + bAdjustZOfStockToPrimaryHand = B.bAdjustZOfStockToPrimaryHand; + bSmoothStockHand = B.bSmoothStockHand; + SmoothingValueForStock = B.SmoothingValueForStock; + StockHandSmoothing = B.StockHandSmoothing; + } + + FBPVirtualStockSettings() + { + StockSnapOffset = FVector(0.f, 0.f, 0.f); + bAdjustZOfStockToPrimaryHand = true; + StockSnapDistance = 35.f; + StockSnapLerpThreshold = 20.0f; + StockLerpValue = 0.0f; + bUseDistanceBasedStockSnapping = true; + SmoothingValueForStock = 0.0f; + bSmoothStockHand = false; + + // Speed up the lerp on fast movements for this + StockHandSmoothing.DeltaCutoff = 20.0f; + StockHandSmoothing.MinCutoff = 5.0f; + + bDebugDrawVirtualStock = false; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FGunTools_AdvSecondarySettings +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings") + bool bUseAdvancedSecondarySettings; + + // Scaler used for handling the smoothing amount, 0.0f is zero smoothing, 1.0f is full smoothing, you should test with full smoothing to get the amount you + // want and then set the smoothing value up until it feels right between the fully smoothed and unsmoothed values. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings", ClampMin = "0.00", UIMin = "0.00", ClampMax = "1.00", UIMax = "1.00")) + float SecondaryGripScaler; + + // If true we will constantly be lerping with the grip scaler instead of only sometimes. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseConstantGripScaler; + + // If true will override custom settings for the smoothing values with the global settings in VRSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseGlobalSmoothingSettings; + + // Used to smooth filter the secondary influence + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing") + FBPEuroLowPassFilter SecondarySmoothing; + + // Whether to scale the secondary hand influence off of distance from grip point + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseSecondaryGripDistanceInfluence; + + // If true, will use the GripInfluenceDeadZone as a constant value instead of calculating the distance and lerping, lets you define a static influence amount. + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bUseSecondaryGripDistanceInfluence")) + // bool bUseGripInfluenceDeadZoneAsConstant; + + // Distance from grip point in local space where there is 100% influence + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseSecondaryGripDistanceInfluence", ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float GripInfluenceDeadZone; + + // Distance from grip point in local space before all influence is lost on the secondary grip (1.0f - 0.0f influence over this range) + // this comes into effect outside of the deadzone + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseSecondaryGripDistanceInfluence", ClampMin = "1.00", UIMin = "1.00", ClampMax = "256.00", UIMax = "256.00")) + float GripInfluenceDistanceToZero; + + FGunTools_AdvSecondarySettings() + { + bUseAdvancedSecondarySettings = false; + SecondaryGripScaler = 0.0f; + bUseGlobalSmoothingSettings = true; + bUseSecondaryGripDistanceInfluence = false; + //bUseGripInfluenceDeadZoneAsConstant(false), + GripInfluenceDeadZone = 50.0f; + GripInfluenceDistanceToZero = 100.0f; + bUseConstantGripScaler = false; + } +}; + + +// A grip script that adds useful fire-arm related features to grips +// Just adding it to the grippable object provides the features without removing standard +// Grip features. +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_GunTools : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_GunTools(const FObjectInitializer& ObjectInitializer); + + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + virtual void OnBeginPlay_Implementation(UObject* CallingOwner) override; + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + //virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + + + // The name of the component that is used to orient the weapon along its primary axis + // If it does not exist then the weapon is assumed to be X+ facing. + // Also used to perform some calculations, make sure it is parented to the gripped object (root component for actors), + // and that the X+ vector of the orientation component is facing the forward direction of the weapon (gun tip for guns, ect). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FName WeaponRootOrientationComponent; + FTransform OrientationComponentRelativeFacing; + FQuat StoredRootOffset; + + // (default false) If true will run through the entire simulation that the owning client uses for the gun. If false, does a lighter and more performant approximation. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "GunSettings") + bool bUseHighQualityRemoteSimulation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings") + FGunTools_AdvSecondarySettings AdvSecondarySettings; + + // Offset to apply to the pivot (good for centering pivot into the palm ect). + // For this to apply to the physical center of mass as well an OrientationComponent needs to be defined + // So that we have a valid directional vector to work off of, otherwise the pivot will be in component space and you + // will have a harder time aligning it if the weapon is off axis (still works, just less intuitive). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pivot") + FVector_NetQuantize100 PivotOffset; + + UFUNCTION(BlueprintCallable, Category = "VirtualStock") + void SetVirtualStockComponent(USceneComponent * NewStockComponent) + { + VirtualStockComponent = NewStockComponent; + } + + UFUNCTION(BlueprintCallable, Category = "VirtualStock") + void SetVirtualStockEnabled(bool bAllowVirtualStock) + { + if (!bUseVirtualStock && bAllowVirtualStock) + ResetStockVariables(); + + bUseVirtualStock = bAllowVirtualStock; + } + + void ResetStockVariables() + { + VirtualStockSettings.StockHandSmoothing.ResetSmoothingFilter(); + } + + void GetVirtualStockTarget(UGripMotionControllerComponent * GrippingController); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VirtualStock") + FVRVirtualStockModeChangedSignature OnVirtualStockModeChanged; + + // Overrides the pivot location to be at this component instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseVirtualStock; + + FTransform MountWorldTransform; + bool bIsMounted; + FTransform RelativeTransOnSecondaryRelease; + TObjectPtr CameraComponent; + + // Overrides the default behavior of using the HMD location for the stock and uses this component instead + UPROPERTY(BlueprintReadWrite, Category = "VirtualStock") + TObjectPtr VirtualStockComponent; + + // Loads the global virtual stock settings on grip (only if locally controlled, you need to manually replicate and store the global settings + // In the character if networked). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseGlobalVirtualStockSettings; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock", meta = (editcondition = "!bUseGlobalVirtualStockSettings")) + FBPVirtualStockSettings VirtualStockSettings; + + // If this gun has recoil + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + bool bHasRecoil; + + // If true then the recoil will be added as a physical force instead of logical blend + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + bool bApplyRecoilAsPhysicalForce; + + // Maximum recoil addition + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilTranslation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilRotation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilScale; + + // Recoil decay rate, how fast it decays back to baseline + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + float DecayRate; + + // Recoil lerp rate, how long it takes to lerp to the target recoil amount (0.0f would be instant) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + float LerpRate; + + // Stores the current amount of recoil + FTransform BackEndRecoilStorage; + + // Stores the target amount of recoil + FTransform BackEndRecoilTarget; + + bool bHasActiveRecoil; + + // Adds a recoil instance to the gun tools, the option location is for if using the physical recoil mode + // Physical recoil is in world space and positional only, logical recoil is in relative space to the mesh itself and uses all + // of the transforms properties. + UFUNCTION(BlueprintCallable, Category = "Recoil") + void AddRecoilInstance(const FTransform & RecoilAddition, FVector Optional_Location = FVector::ZeroVector); + + UFUNCTION(BlueprintCallable, Category = "Recoil") + void ResetRecoil(); + + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + // Applies the two hand modifier, broke this out into a function so that we can handle late updates + static void ApplyTwoHandModifier(FTransform & OriginalTransform) + { + + + } + + // Returns the smoothed value now + inline FVector GunTools_ApplySmoothingAndLerp(FBPActorGripInformation & Grip, FVector &frontLoc, FVector & frontLocOrig, float DeltaTime, bool bSkipHighQualitySimulations) + { + FVector SmoothedValue = frontLoc; + + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) // Lerp into the new grip to smooth the transition + { + if (!bSkipHighQualitySimulations && AdvSecondarySettings.SecondaryGripScaler < 1.0f) + { + SmoothedValue = AdvSecondarySettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + frontLoc = FMath::Lerp(frontLoc, SmoothedValue, AdvSecondarySettings.SecondaryGripScaler); + + } + //Default_ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + } + else if (!bSkipHighQualitySimulations && AdvSecondarySettings.bUseAdvancedSecondarySettings && AdvSecondarySettings.bUseConstantGripScaler) // If there is a frame by frame lerp + { + SmoothedValue = AdvSecondarySettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + frontLoc = FMath::Lerp(frontLoc, SmoothedValue, AdvSecondarySettings.SecondaryGripScaler); + } + + return SmoothedValue; + } +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h new file mode 100644 index 0000000..ca62911 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h @@ -0,0 +1,114 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "GS_InteractibleSettings.generated.h" + +class UGripMotionControllerComponent; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPGS_InteractionSettings +{ + GENERATED_BODY() +public: + + bool bHasValidBaseTransform; // So we don't have to equals the transform + FTransform BaseTransform; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + uint32 bLimitsInLocalSpace : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + uint32 bGetInitialPositionsOnBeginPlay : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitX : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitY : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitZ : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitPitch : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitYaw : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitRoll : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bIgnoreHandRotation : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear", meta = (editcondition = "!bGetInitialPositionsOnBeginPlay")) + FVector_NetQuantize100 InitialLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + FVector_NetQuantize100 MinLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + FVector_NetQuantize100 MaxLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular", meta = (editcondition = "!bGetInitialPositionsOnBeginPlay")) + FRotator InitialAngularTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + FRotator MinAngularTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + FRotator MaxAngularTranslation; + + FBPGS_InteractionSettings() : + bLimitsInLocalSpace(true), + bGetInitialPositionsOnBeginPlay(true), + bLimitX(false), + bLimitY(false), + bLimitZ(false), + bLimitPitch(false), + bLimitYaw(false), + bLimitRoll(false), + bIgnoreHandRotation(false), + InitialLinearTranslation(FVector::ZeroVector), + MinLinearTranslation(FVector::ZeroVector), + MaxLinearTranslation(FVector::ZeroVector), + InitialAngularTranslation(FRotator::ZeroRotator), + MinAngularTranslation(FRotator::ZeroRotator), + MaxAngularTranslation(FRotator::ZeroRotator) + { + BaseTransform = FTransform::Identity; + bHasValidBaseTransform = false; + } +}; + +// A Grip script that overrides the default grip behavior and adds custom clamping logic instead, +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_InteractibleSettings : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_InteractibleSettings(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "InteractionSettings") + FBPGS_InteractionSettings InteractionSettings; + + virtual void OnBeginPlay_Implementation(UObject * CallingOwner) override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Flags the the interaction settings so that it will regenerate removing the hand rotation. + // Use this if you just changed the relative hand transform. + UFUNCTION(BlueprintCallable, Category = "InteractionSettings") + void RemoveHandRotation() + { + // Flag the base transform to be re-applied + InteractionSettings.bHasValidBaseTransform = false; + } + + void RemoveRelativeRotation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h new file mode 100644 index 0000000..78d83c2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h @@ -0,0 +1,69 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "VRBPDatatypes.h" +#include "Curves/CurveFloat.h" +#include "GS_LerpToHand.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRLerpToHandFinishedSignature); + + +// A grip script that causes new grips to lerp to the hand (from their current position to where they are supposed to sit). +// It turns off when the lerp is complete. +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_LerpToHand : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_LerpToHand(const FObjectInitializer& ObjectInitializer); + + float CurrentLerpTime; + float LerpSpeed; + + // If the initial grip distance is closer than this value then the lerping will not be performed. + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MinDistanceForLerp; + + // How many seconds the lerp should take + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float LerpDuration; + + // The minimum speed (in UU per second) that that the lerp should have across the initial grip distance + // Will speed the LerpSpeed up to try and maintain this initial speed if required + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MinSpeedForLerp; + + // The maximum speed (in UU per second) that the lerp should have across the initial grip distance + // Will slow the LerpSpeed down to try and maintain this initial speed if required + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MaxSpeedForLerp; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + EVRLerpInterpolationMode LerpInterpolationMode; + + UPROPERTY(BlueprintAssignable, Category = "LerpEvents") + FVRLerpToHandFinishedSignature OnLerpToHandBegin; + + UPROPERTY(BlueprintAssignable, Category = "LerpEvents") + FVRLerpToHandFinishedSignature OnLerpToHandFinished; + + // Whether to use a curve map to map the lerp to + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpCurve") + bool bUseCurve; + + // The curve to follow when using a curve map, only uses from 0.0 - 1.0 of the curve timeline and maps it across the entire duration + UPROPERTY(Category = "LerpCurve", EditAnywhere, meta = (editcondition = "bUseCurve")) + FRuntimeFloatCurve OptionalCurveToFollow; + + FTransform OnGripTransform; + uint8 TargetGrip; + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) override; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h new file mode 100644 index 0000000..151e1fe --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h @@ -0,0 +1,319 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GripScripts/GS_Default.h" +#include "GS_Melee.generated.h" + + +// The type of melee hit zone we are +UENUM(BlueprintType) +enum class EVRMeleeZoneType : uint8 +{ + // This zone is only valid for stabs + VRPMELLE_ZONETYPE_Stab UMETA(DisplayName = "Stab"), + + // This zone is only valid for hits + VRPMELLE_ZONETYPE_Hit UMETA(DisplayName = "Hit"), + + // This zone is valid for both stabs and hits + VRPMELLE_ZONETYPE_StabAndHit UMETA(DisplayName = "StabAndHit") + +}; + +// The type of COM selection to use +UENUM(BlueprintType) +enum class EVRMeleeComType : uint8 +{ + // Does not set COM + VRPMELEECOM_Normal UMETA(DisplayName = "Normal"), + + // Sets COM to between hands + VRPMELEECOM_BetweenHands UMETA(DisplayName = "BetweenHands"), + + // Uses the primary hand as com location + VRPMELEECOM_PrimaryHand UMETA(DisplayName = "PrimaryHand") +}; + +// The type of primary hand selection to use +UENUM(BlueprintType) +enum class EVRMeleePrimaryHandType : uint8 +{ + // Uses the rearmost hand as the primary hand + VRPHAND_Rear UMETA(DisplayName = "Rear"), + + // Uses the foremost hand as the primary hand + VRPHAND_Front UMETA(DisplayName = "Front"), + + // Uses the first slotted hand as the primary hand + // If neither are slotted then its first come first serve and both hannds load the secondary settings + VRPHAND_Slotted UMETA(DisplayName = "Slotted") +}; + +// A Lodge component data struct +USTRUCT(BlueprintType, Category = "Lodging") +struct VREXPANSIONPLUGIN_API FBPHitSurfaceProperties +{ + GENERATED_BODY() +public: + + // Does this surface type allow penetration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + bool bSurfaceAllowsPenetration; + + // Scaler to damage applied from hitting this surface with blunt damage + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float BluntDamageScaler; + + // Scaler to damage applied from hitting this surface with sharp damage + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float SharpDamageScaler; + + // Alters the stab velocity to let you make it harder or easier to stab this surface + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float StabVelocityScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + TEnumAsByte SurfaceType; + + FBPHitSurfaceProperties() + { + // Default to true on this + bSurfaceAllowsPenetration = true; + BluntDamageScaler = 1.f; + SharpDamageScaler = 1.f; + StabVelocityScaler = 1.f; + SurfaceType = EPhysicalSurface::SurfaceType_Default; + } +}; + +// A Lodge component data struct +USTRUCT(BlueprintType, Category = "Lodging") +struct VREXPANSIONPLUGIN_API FBPLodgeComponentInfo +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + FName ComponentName; + + // Type of collision zone we are + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + EVRMeleeZoneType ZoneType; + + // If true than we will calculate hit impulse off of its total value and not just off of it axially aligned to the forward of this body + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + bool bIgnoreForwardVectorForHitImpulse; + + // For end users to provide a base damage per zone if they want + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + float DamageScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + float PenetrationDepth; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + bool bAllowPenetrationInReverseAsWell; + + // This is the impulse velocity (along forward axis of component) required to throw an OnPenetrated event from a PenetrationNotifierComponent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float PenetrationVelocity; + + // This is the impulse velocity required to throw an OnHit event from a PenetrationNotifierComponent (If a stab didn't take place) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float MinimumHitVelocity; + + // The acceptable range of the dot product of the forward vector and the impact normal to define a valid facing + // Subtracted from the 1.0f forward facing value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float AcceptableForwardProductRange; + + // The acceptable range of the dot product of the forward vector and the impact normal to define a valid facing + // Subtracted from the 1.0f forward facing value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float AcceptableForwardProductRangeForHits; + + FBPLodgeComponentInfo() + { + ComponentName = NAME_None; + ZoneType = EVRMeleeZoneType::VRPMELLE_ZONETYPE_StabAndHit; + bIgnoreForwardVectorForHitImpulse = false; + DamageScaler = 0.f; + PenetrationDepth = 100.f; + bAllowPenetrationInReverseAsWell = false; + PenetrationVelocity = 8000.f; + MinimumHitVelocity = 1000.f; + AcceptableForwardProductRange = 0.1f; + AcceptableForwardProductRangeForHits = 0.1f; + TargetComponent = nullptr; + } + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "LodgeComponentInfo") + TObjectPtr TargetComponent; + + FORCEINLINE bool operator==(const FName& Other) const + { + return (ComponentName == Other); + } + +}; + + +// Event thrown when we the melee weapon becomes lodged +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FVROnMeleeShouldLodgeSignature, FBPLodgeComponentInfo, LogComponent, AActor *, OtherActor, UPrimitiveComponent *, OtherComp, ECollisionChannel, OtherCompCollisionChannel, FBPHitSurfaceProperties, HitSurfaceProperties, FVector, NormalImpulse, const FHitResult&, Hit); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FVROnMeleeOnHit, FBPLodgeComponentInfo, LogComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, ECollisionChannel, OtherCompCollisionChannel, FBPHitSurfaceProperties, HitSurfaceProperties, FVector, NormalImpulse, const FHitResult&, Hit); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FVROnMeleeInvalidHitSignature, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit); + +/** +* A Melee grip script that hands multi hand interactions and penetration notifications* +* The per surface damage and penetration options have been moved to the project settings unless the per script override is set +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_Melee : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_Melee(const FObjectInitializer& ObjectInitializer); + + UFUNCTION() + virtual void OnLodgeHitCallback(AActor* SelfActor, AActor* OtherActor, FVector NormalImpulse, const FHitResult& Hit); + + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void SetIsLodged(bool IsLodged, UPrimitiveComponent * LodgeComponent) + { + bIsLodged = IsLodged; + LodgedComponent = LodgeComponent; + } + + bool bIsLodged; + TWeakObjectPtr LodgedComponent; + + //virtual void Tick(float DeltaTime) override; + + // Thrown if we should lodge into a hit object + UPROPERTY(BlueprintAssignable, Category = "Melee|Lodging") + FVROnMeleeShouldLodgeSignature OnShouldLodgeInObject; + + // Thrown if we hit something we can damage + UPROPERTY(BlueprintAssignable, Category = "Melee|Hit") + FVROnMeleeOnHit OnMeleeHit; + + // Fired when a hit is invalid (hit something that isn't flagged for damage or stabbing or was below the damage or stab threshold) + UPROPERTY(BlueprintAssignable, Category = "Melee|Hit") + FVROnMeleeInvalidHitSignature OnMeleeInvalidHit; + + // Always tick for penetration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + bool bAlwaysTickPenetration; + + // Only penetrate with two hands on the weapon + // Mostly for very large weapons + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + bool bOnlyPenetrateWithTwoHands; + + // A list of surface types that allow penetration and their properties + // If empty then the script will use the global settings, if filled with anything then it will override the global settings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + TArray OverrideMeleeSurfaceSettings; + +// FVector RollingVelocityAverage; + //FVector RollingAngVelocityAverage; + + // The name of the component that is used to orient the weapon along its primary axis + // If it does not exist then the weapon is assumed to be X+ facing. + // Also used to perform some calculations, make sure it is parented to the gripped object (root component for actors). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FName WeaponRootOrientationComponent; + FTransform OrientationComponentRelativeFacing; + + // UpdateHand location on the shaft in the X axis + // If primary hand is false then it will do the secondary hand + // World location is of the pivot generally, I have it passing in so people can snap + // LocDifference returns the relative distance of the change in position (or zero if there was none). + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void UpdateHandPosition(FBPGripPair HandPair, FVector HandWorldPosition, FVector & LocDifference); + + // UpdateHand location and rotation on the shaft in the X axis + // If primary hand is false then it will do the secondary hand + // World location is of the pivot generally, I have it passing in so people can snap + // LocDifference returns the relative distance of the change in position (or zero if there was none). + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void UpdateHandPositionAndRotation(FBPGripPair HandPair, FTransform HandWorldTransform, FVector& LocDifference, float& RotDifference, bool bUpdateLocation = true, bool bUpdateRotation = true); + + + // This is a built list of components that act as penetration notifiers, they will have their OnHit bound too and we will handle penetration logic + // off of it. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + TArray PenetrationNotifierComponents; + + bool bCheckLodge; + bool bIsHeld; + + FVector LastRelativePos; + FVector RelativeBetweenGripsCenterPos; + + // When true, will auto set the primary and secondary hands by the WeaponRootOrientationComponents X Axis distance. + // Smallest value along the X Axis will be considered the primary hand. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + bool bAutoSetPrimaryAndSecondaryHands; + + // If we couldn't decide on a true valid primary hand then this will be false and we will load secondary settings for both + bool bHasValidPrimaryHand; + + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void SetPrimaryAndSecondaryHands(FBPGripPair & PrimaryGrip, FBPGripPair & SecondaryGrip); + + // Which method of primary hand to select + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + EVRMeleePrimaryHandType PrimaryHandSelectionType; + + UPROPERTY(BlueprintReadOnly, Category = "Weapon Settings") + FBPGripPair PrimaryHand; + + UPROPERTY(BlueprintReadOnly, Category = "Weapon Settings") + FBPGripPair SecondaryHand; + + // If true we will use the primary hands grip settings when we only have one hand gripping instead of the objects VRGripInterfaces settings + UPROPERTY(BlueprintReadOnly, Category = "Weapon Settings") + bool bUsePrimaryHandSettingsWithOneHand; + + // To select the type of com setting to use + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + EVRMeleeComType COMType; + + FTransform ObjectRelativeGripCenter; + + void SetComBetweenHands(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo); + + + // Grip settings to use on the primary hand when multiple grips are active + // Falls back to the standard grip settings when only one grip is active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FBPAdvancedPhysicsHandleSettings PrimaryHandPhysicsSettings; + + // Grip settings to use on the secondary hand when multiple grips are active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FBPAdvancedPhysicsHandleSettings SecondaryHandPhysicsSettings; + + + void UpdateDualHandInfo(); + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + virtual void OnBeginPlay_Implementation(UObject* CallingOwner) override; + virtual void OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) override; + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* Controller, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + virtual bool Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller) override; + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h new file mode 100644 index 0000000..c300eee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GameFramework/WorldSettings.h" +#include "GripScripts/GS_Default.h" +#include "GS_Physics.generated.h" + +/** +* A pure physics multi hand interaction grip script, expects that bAllowMultiGrips is set on the parent object* +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_Physics : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_Physics(const FObjectInitializer& ObjectInitializer); + + // Grip settings to use when a single hand is gripping, overrides interface defaults + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings") + FBPAdvancedPhysicsHandleSettings SingleHandPhysicsSettings; + + // Grip settings to use when multiple hands are gripping + // Overrides interface defaults + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings") + FBPAdvancedPhysicsHandleSettings MultiHandPhysicsSettings; + + + void UpdateDualHandInfo(UGripMotionControllerComponent* GrippingController = nullptr, bool bRecreate = true); + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + //virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h new file mode 100644 index 0000000..36989ab --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h @@ -0,0 +1,273 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "UObject/Object.h" +#include "VRBPDatatypes.h" +#include "Tickable.h" + +#include "VRGripScriptBase.generated.h" + +class UGripMotionControllerComponent; +class UVRGripInterface; +class UPrimitiveComponent; +class AActor; + + +UENUM(Blueprintable) +enum class EGSTransformOverrideType : uint8 +{ + /** Does not alter the world transform */ + None, + + /* Overrides the world transform */ + OverridesWorldTransform, + + /* Modifies the world transform*/ + ModifiesWorldTransform +}; + +UCLASS(NotBlueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, Abstract, ClassGroup = (VRExpansionPlugin), HideCategories = DefaultSettings) +class VREXPANSIONPLUGIN_API UVRGripScriptBase : public UObject, public FTickableGameObject +{ + GENERATED_BODY() +public: + + UVRGripScriptBase(const FObjectInitializer& ObjectInitializer); + + // Gets the first grip script of the specified type in this object, do NOT call this on tick, save out and store the reference given + UFUNCTION(BlueprintCallable, Category = "VRGripScript|Functions", meta = (WorldContext = "WorldContextObject", bIgnoreSelf = "true", DisplayName = "GetGripScriptByClass", ExpandEnumAsExecs = "Result")) + static UVRGripScriptBase* GetGripScriptByClass(UObject* WorldContextObject, TSubclassOf GripScriptClass, EBPVRResultSwitch& Result); + + bool IsSupportedForNetworking() const override + { + return true; + //return bRequiresReplicationSupport || Super::IsSupportedForNetworking(); + } + // I don't need to do this, there should be no dynamic script spawning and they are all name stable by default + + // Returns if the script is currently active and should be used + bool IsScriptActive(); + + // Is currently active helper variable, returned from IsScriptActive() + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bIsActive; + + // Returns if the script is going to modify the world transform of the grip + EGSTransformOverrideType GetWorldTransformOverrideType(); + + // Whether this script overrides or modifies the world transform + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + EGSTransformOverrideType WorldTransformOverrideType; + + // Returns if the script wants auto drop to be ignored + FORCEINLINE bool Wants_DenyAutoDrop() + { + return bDenyAutoDrop; + } + + // Returns if we want to deny auto dropping + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bDenyAutoDrop; + + // Returns if the script wants to force a drop + FORCEINLINE bool Wants_ToForceDrop() + { + return bForceDrop; + } + + // Returns if we want to force a drop + UPROPERTY(BlueprintReadWrite, Category = "GSSettings") + bool bForceDrop; + + // Flags the grip to be dropped as soon as possible + UFUNCTION(BlueprintCallable, Category = "VRGripScript") + void ForceGripToDrop() + { + bForceDrop = true; + } + + // Returns if the script wants to deny late updates + FORCEINLINE bool Wants_DenyLateUpdates() + { + return bDenyLateUpdates; + } + + // Returns if we want to inject changes prior to the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bDenyLateUpdates; + + // Returns if the script wants auto drop to be ignored + FORCEINLINE bool InjectPrePhysicsHandle() + { + return bInjectPrePhysicsHandle; + } + + // Returns if we want to inject changes prior to the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bInjectPrePhysicsHandle; + + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose); + + // Returns if we want to inject changes after the physics handle + FORCEINLINE bool InjectPostPhysicsHandle() + { + return bInjectPostPhysicsHandle; + } + + // Returns if we want to inject changes after the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bInjectPostPhysicsHandle; + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo); + + // Returns if the script is currently active and should be used + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripScript") + bool Wants_DenyTeleport(UGripMotionControllerComponent * Controller); + virtual bool Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller); + + virtual void GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const override; + + // doesn't currently compile in editor builds, not sure why the linker is screwing up there but works elsewhere + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker); + virtual bool CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack) override; + virtual int32 GetFunctionCallspace(UFunction * Function, FFrame * Stack) override; + + // FTickableGameObject functions + + + // If true then this scrip can tick when bAllowticking is true + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "TickSettings") + bool bCanEverTick; + + // If true and we bCanEverTick, then will fire off the tick function + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "TickSettings") + bool bAllowTicking; + + // Set whether the grip script can tick or not + UFUNCTION(BlueprintCallable, Category = "TickSettings") + void SetTickEnabled(bool bTickEnabled); + + /** + * Function called every frame on this GripScript. Override this function to implement custom logic to be executed every frame. + * Only executes if bCanEverTick is true and bAllowTicking is true + * + * @param DeltaTime - The time since the last tick. + */ + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override; + virtual UWorld* GetTickableGameObjectWorld() const override; + virtual bool IsTickableInEditor() const; + virtual bool IsTickableWhenPaused() const override; + virtual ETickableTickType GetTickableTickType() const; + virtual TStatId GetStatId() const override; + virtual UWorld* GetWorld() const override; + + // End tickable object information + + + // Returns the expected grip transform (relative * controller + addition) + UFUNCTION(BlueprintPure, Category = "VRGripScript") + FTransform GetGripTransform(const FBPActorGripInformation &Grip, const FTransform & ParentTransform); + + // Returns the current world transform of the owning object (or root comp of if it is an actor) + UFUNCTION(BlueprintPure, Category = "VRGripScript") + FTransform GetParentTransform(bool bGetWorldTransform = true, FName BoneName = NAME_None); + + // Returns the scene component of the parent, either being the parent itself or the root comp of it. + // Nullptr if there is no valid scene component + UFUNCTION(BlueprintCallable, Category = "VRGripScript") + USceneComponent* GetParentSceneComp(); + + // Returns the root body instance of the parent + FBodyInstance * GetParentBodyInstance(FName OptionalBoneName = NAME_None); + + // Returns the parent component or actor to this + UFUNCTION(BlueprintPure, Category = "VRGripScript") + UObject * GetParent(); + + // Returns the owning actor + UFUNCTION(BlueprintPure, Category = "VRGripScript") + AActor * GetOwner(); + + // If the owning actor has authority on this connection + UFUNCTION(BlueprintPure, Category = "VRGripScript") + bool HasAuthority(); + + // If the owning actor is on the server on this connection + UFUNCTION(BlueprintPure, Category = "VRGripScript") + bool IsServer(); + + void EndPlay(const EEndPlayReason::Type EndPlayReason); + + // Not all scripts will require this function, specific ones that use things like Lever logic however will. Best to call it. + // Grippables will automatically call this, however if you manually spawn a grip script during play or you make your own + // Interfaced grip object and give it grippables, YOU will be required to call this event on them. + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnEndPlay(const EEndPlayReason::Type EndPlayReason); + virtual void OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason); + + void BeginPlay(UObject * CallingOwner); + bool bAlreadyNotifiedPlay = false; + virtual void PostInitProperties() override; + + // Not all scripts will require this function, specific ones that use things like Lever logic however will. Best to call it. + // Grippables will automatically call this, however if you manually spawn a grip script during play or you make your own + // Interfaced grip object and give it grippables, YOU will be required to call this event on them. + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnBeginPlay(UObject * CallingOwner); + virtual void OnBeginPlay_Implementation(UObject * CallingOwner); + + // Overrides or Modifies the world transform with this grip script + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + bool GetWorldTransform(UGripMotionControllerComponent * GrippingController, float DeltaTime, UPARAM(ref) FTransform & WorldTransform, const FTransform &ParentTransform, UPARAM(ref) FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport); + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport); + + // Event triggered on the interfaced object when gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when secondary gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGrip(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when secondary grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGripRelease(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + + + virtual bool CallCorrect_GetWorldTransform(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) + { + return GetWorldTransform_Implementation(OwningController, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + } +}; + + +UCLASS(Blueprintable, Abstract, ClassGroup = (VRExpansionPlugin), ShowCategories = DefaultSettings) +class VREXPANSIONPLUGIN_API UVRGripScriptBaseBP : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + virtual bool CallCorrect_GetWorldTransform(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override + { + return GetWorldTransform(OwningController, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + } + + virtual void Tick(float DeltaTime) override; + + /** Event called every frame if ticking is enabled */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Tick")) + void ReceiveTick(float DeltaSeconds); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h new file mode 100644 index 0000000..110e8ee --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h @@ -0,0 +1,273 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +#include "GameFramework/Actor.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +//#include "Engine/Engine.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableActor : public AActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableActor(const FObjectInitializer& ObjectInitializer); + + ~AGrippableActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR & newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if ((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Handle fixing some bugs and issues with ReplicateMovement being off + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsGarbage(bool bModify) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h new file mode 100644 index 0000000..f1a94e5 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h @@ -0,0 +1,202 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "Components/BoxComponent.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "GrippableBoxComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableBoxComponent : public UBoxComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableBoxComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableBoxComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h new file mode 100644 index 0000000..573e324 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h @@ -0,0 +1,199 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/CapsuleComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableCapsuleComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableCapsuleComponent : public UCapsuleComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableCapsuleComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableCapsuleComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h new file mode 100644 index 0000000..68aaf3b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h @@ -0,0 +1,28 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "GrippableCharacter.generated.h" + +class UGrippableSkeletalMeshComponent; + +UCLASS() +class VREXPANSIONPLUGIN_API AGrippableCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + AGrippableCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // A reference to the grippable character that can be used instead of casting the root, BP doesn't like the class override. + UPROPERTY(Category = GrippableCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr GrippableMeshReference; + + // A Custom bone to use on the character mesh as the originator for the perception systems sight sense + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI") + FName ViewOriginationSocket; + + virtual void GetActorEyesViewPoint(FVector& Location, FRotator& Rotation) const override; + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h new file mode 100644 index 0000000..53d3216 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h @@ -0,0 +1,33 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/EngineTypes.h" +#include "Engine/ReplicatedState.h" +#include "GrippableDataTypes.generated.h" + +// A version of the attachment structure that include welding data +USTRUCT() +struct VREXPANSIONPLUGIN_API FRepAttachmentWithWeld : public FRepAttachment +{ +public: + GENERATED_BODY() + + // Add in the is welded property + UPROPERTY() + bool bIsWelded; + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + + FRepAttachmentWithWeld(); +}; + +template<> +struct TStructOpsTypeTraits< FRepAttachmentWithWeld > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true//, + //WithNetSharedSerialization = true, + }; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h new file mode 100644 index 0000000..ba33b19 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h @@ -0,0 +1,168 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Physics/PhysicsInterfaceUtils.h" +#include "PhysicsReplication.h" + +#include "Engine/ReplicatedState.h" +#include "PhysicsReplicationInterface.h" +#include "Physics/PhysicsInterfaceDeclares.h" +#include "PhysicsProxy/SingleParticlePhysicsProxyFwd.h" +#include "Chaos/Particles.h" +#include "Chaos/PhysicsObject.h" +#include "Chaos/SimCallbackObject.h" + + +#include "GrippablePhysicsReplication.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPhysicsReplicationDelegate, void, Return); + +/*static TAutoConsoleVariable CVarEnableCustomVRPhysicsReplication( + TEXT("vr.VRExpansion.EnableCustomVRPhysicsReplication"), + 0, + TEXT("Enable valves input controller that overrides legacy input.\n") + TEXT(" 0: use the engines default input mapping (default), will also be default if vr.SteamVR.EnableVRInput is enabled\n") + TEXT(" 1: use the valve input controller. You will have to define input bindings for the controllers you want to support."), + ECVF_ReadOnly);*/ + +//#if PHYSICS_INTERFACE_PHYSX +//struct FAsyncPhysicsRepCallbackDataVR; +//class FPhysicsReplicationAsyncCallbackVR; + +class FPhysicsReplicationAsyncVR : public Chaos::TSimCallbackObject< + FPhysicsReplicationAsyncInput, + Chaos::FSimCallbackNoOutput, + Chaos::ESimCallbackOptions::Presimulate> +{ + virtual FName GetFNameForStatId() const override; + virtual void OnPreSimulate_Internal() override; + virtual void ApplyTargetStatesAsync(const float DeltaSeconds, const FPhysicsRepErrorCorrectionData& ErrorCorrection, const TArray& TargetStates); + + // Replication functions + virtual void DefaultReplication_DEPRECATED(Chaos::FRigidBodyHandle_Internal* Handle, const FPhysicsRepAsyncInputData& State, const float DeltaSeconds, const FPhysicsRepErrorCorrectionData& ErrorCorrection); + virtual bool DefaultReplication(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds); + virtual bool PredictiveInterpolation(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds); + virtual bool ResimulationReplication(Chaos::FPBDRigidParticleHandle* Handle, FReplicatedPhysicsTargetAsync& Target, const float DeltaSeconds); + +private: + float LatencyOneWay; + FRigidBodyErrorCorrection ErrorCorrectionDefault; + TMap ObjectToTarget; + +private: + void UpdateAsyncTarget(const FPhysicsRepAsyncInputData& Input); + void UpdateRewindDataTarget(const FPhysicsRepAsyncInputData& Input); + +public: + void Setup(FRigidBodyErrorCorrection ErrorCorrection) + { + ErrorCorrectionDefault = ErrorCorrection; + } +}; + + + +class FPhysicsReplicationVR : public FPhysicsReplication +{ +public: + + FPhysScene* PhysSceneVR; + + FPhysicsReplicationVR(FPhysScene* PhysScene); + ~FPhysicsReplicationVR(); + static bool IsInitialized(); + + virtual void OnTick(float DeltaSeconds, TMap, FReplicatedPhysicsTarget>& ComponentsToTargets) override; + + virtual bool ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float PingSecondsOneWay, int32 LocalFrame, int32 NumPredictedFrames) override; + virtual bool ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float PingSecondsOneWay, bool* bDidHardSnap = nullptr) override; + + virtual void SetReplicatedTarget(UPrimitiveComponent* Component, FName BoneName, const FRigidBodyState& ReplicatedTarget, int32 ServerFrame) override; + void SetReplicatedTargetVR(Chaos::FPhysicsObject* PhysicsObject, const FRigidBodyState& ReplicatedTarget, int32 ServerFrame, EPhysicsReplicationMode ReplicationMode); + + TArray ReplicatedTargetsQueueVR; + FPhysicsReplicationAsyncVR* PhysicsReplicationAsyncVR; + FPhysicsReplicationAsyncInput* AsyncInputVR; //async data being written into before we push into callback + + void PrepareAsyncData_ExternalVR(const FRigidBodyErrorCorrection& ErrorCorrection); //prepare async data for writing. Call on external thread (i.e. game thread) +}; + +class IPhysicsReplicationFactoryVR : public IPhysicsReplicationFactory +{ +public: + + virtual TUniquePtr CreatePhysicsReplication(FPhysScene* OwningPhysScene) override + { + return TUniquePtr(new FPhysicsReplicationVR(OwningPhysScene)); + } + + /*virtual FPhysicsReplication* Create(FPhysScene* OwningPhysScene) + { + return new FPhysicsReplicationVR(OwningPhysScene); + } + + virtual void Destroy(FPhysicsReplication* PhysicsReplication) + { + if (PhysicsReplication) + delete PhysicsReplication; + }*/ +}; + +//#endif + +USTRUCT() +struct VREXPANSIONPLUGIN_API FRepMovementVR : public FRepMovement +{ + GENERATED_USTRUCT_BODY() +public: + + FRepMovementVR(); + FRepMovementVR(FRepMovement& other); + void CopyTo(FRepMovement& other) const; + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + bool GatherActorsMovement(AActor* OwningActor); +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +USTRUCT(BlueprintType) +struct VREXPANSIONPLUGIN_API FVRClientAuthReplicationData +{ + GENERATED_BODY() +public: + + // If True and we are using a client auth grip type then we will replicate our throws on release + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRReplication") + bool bUseClientAuthThrowing; + + // Rate that we will be sending throwing events to the server, not replicated, only serialized + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadOnly, Category = "VRReplication", meta = (ClampMin = "0", UIMin = "0", ClampMax = "100", UIMax = "100")) + int32 UpdateRate; + + FTimerHandle ResetReplicationHandle; + FTransform LastActorTransform; + float TimeAtInitialThrow; + bool bIsCurrentlyClientAuth; + + FVRClientAuthReplicationData() : + bUseClientAuthThrowing(false), + UpdateRate(30), + LastActorTransform(FTransform::Identity), + TimeAtInitialThrow(0.0f), + bIsCurrentlyClientAuth(false) + { + + } +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h new file mode 100644 index 0000000..d2fcd0e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h @@ -0,0 +1,299 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableSkeletalMeshActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UOptionalRepSkeletalMeshComponent : public USkeletalMeshComponent +{ + GENERATED_BODY() + +public: + UOptionalRepSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + +public: + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + + // Weld Fix until epic fixes it #TODO: check back in on this and remove and the asset include when epic fixes it + virtual void GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) override; + virtual FBodyInstance* GetBodyInstance(FName BoneName = NAME_None, bool bGetWelded = true, int32 Index = INDEX_NONE) const override; +}; + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableSkeletalMeshActor : public ASkeletalMeshActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer); + + + ~AGrippableSkeletalMeshActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch* Bunch, FReplicationFlags* RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR& newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if ((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsGarbage(bool bModify) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h new file mode 100644 index 0000000..19e861d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h @@ -0,0 +1,205 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SkeletalMeshComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableSkeletalMeshComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableSkeletalMeshComponent : public USkeletalMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableSkeletalMeshComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + // Weld Fix until epic fixes it #TODO: check back in on this and remove and the asset include when epic fixes it + virtual void GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) override; + virtual FBodyInstance* GetBodyInstance(FName BoneName = NAME_None, bool bGetWelded = true, int32 Index = INDEX_NONE) const override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h new file mode 100644 index 0000000..81fd861 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h @@ -0,0 +1,201 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SphereComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableSphereComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableSphereComponent : public USphereComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableSphereComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableSphereComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h new file mode 100644 index 0000000..36c3a1c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h @@ -0,0 +1,298 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableStaticMeshActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent,ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UOptionalRepStaticMeshComponent : public UStaticMeshComponent +{ + GENERATED_BODY() + +public: + UOptionalRepStaticMeshComponent(const FObjectInitializer& ObjectInitializer); + + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; +}; + + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableStaticMeshActor : public AStaticMeshActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableStaticMeshActor(const FObjectInitializer& ObjectInitializer); + + ~AGrippableStaticMeshActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + // From IVRReplicationInterface + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR & newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsGarbage(bool bModify) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h new file mode 100644 index 0000000..a16ea6d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h @@ -0,0 +1,200 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableStaticMeshComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent,ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableStaticMeshComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableStaticMeshComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableStaticMeshComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h new file mode 100644 index 0000000..7803102 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h @@ -0,0 +1,447 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SceneComponent.h" +#include "Animation/AnimInstance.h" +#include "Misc/Guid.h" +#include "HandSocketComponent.generated.h" + +class USkeletalMeshComponent; +class UPoseableMeshComponent; +class USkeletalMesh; +class UGripMotionControllerComponent; +class UAnimSequence; +struct FPoseSnapshot; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRHandSocketComponent, Log, All); + +// Custom serialization version for the hand socket component +struct VREXPANSIONPLUGIN_API FVRHandSocketCustomVersion +{ + enum Type + { + // Before any version changes were made in the plugin + BeforeCustomVersionWasAdded = 0, + + // Added a set state tracker to handle in editor construction edge cases + HandSocketStoringSetState = 1, + + // ------------------------------------------------------ + VersionPlusOne, + LatestVersion = VersionPlusOne - 1 + }; + + // The GUID for this custom version number + const static FGuid GUID; + +private: + FVRHandSocketCustomVersion() {} +}; + + +UENUM(BlueprintType) +namespace EVRAxis +{ + enum Type + { + X, + Y, + Z + }; +} + +/** +* A base class for custom hand socket objects +* Not directly blueprint spawnable as you are supposed to subclass this to add on top your own custom data +*/ + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRHandPoseBonePair +{ + GENERATED_BODY() +public: + + // Distance to offset to get center of waist from tracked parent location + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FName BoneName; + + // Initial "Resting" location of the tracker parent, assumed to be the calibration zero + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FQuat DeltaPose; + + FBoneReference ReferenceToConstruct; + + FBPVRHandPoseBonePair() + { + BoneName = NAME_None; + DeltaPose = FQuat::Identity; + } + + FORCEINLINE bool operator==(const FName& Other) const + { + return (BoneName == Other); + } +}; + +UCLASS(Blueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = ("Component Tick", Events, Physics, Lod, "Asset User Data", Collision)) +class VREXPANSIONPLUGIN_API UHandSocketComponent : public USceneComponent, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + + UHandSocketComponent(const FObjectInitializer& ObjectInitializer); + ~UHandSocketComponent(); + + //static get socket compoonent + + //Axis to mirror on for this socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring|Advanced") + TEnumAsByte MirrorAxis; + + // Axis to flip on when mirroring this socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring|Advanced") + TEnumAsByte FlipAxis; + + // Relative placement of the hand to this socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, /*DuplicateTransient,*/ Category = "Hand Socket Data") + FTransform HandRelativePlacement; + + // Target Slot Prefix + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + FName SlotPrefix; + + // If true the hand meshes relative transform will be de-coupled from the hand socket + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Hand Socket Data") + bool bDecoupleMeshPlacement; + + // If true we should only be used to snap mesh to us, not for the actual socket transform + // Will act like free gripping but the mesh will snap into position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bOnlySnapMesh; + + // If true the end user should only pull the hand pose, not its transform from this component + // This is up to the end user to make use of as its part of the query steps. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bOnlyUseHandPose; + + // If true we will not create the mesh relative transform using the attach socket we are attached too + // Useful in cases where you aren't doing per bone gripping but want the socket to follow a bone that is animating + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bIgnoreAttachBone; + + // If true then this socket is left hand dominant and will flip for the right hand instead + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Hand Socket Data") + bool bLeftHandDominant; + + // If true we will mirror ourselves automatically for the off hand + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring", meta = (DisplayName = "Flip For Off Hand")) + bool bFlipForLeftHand; + + // If true, when we mirror the hand socket it will only mirror rotation, not position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring", meta = (editcondition = "bFlipForLeftHand")) + bool bOnlyFlipRotation; + + // If true then this hand socket will always be considered "in range" and checked against others for lowest distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bAlwaysInRange; + + // If true and there are multiple hand socket components in range with this setting + // Then the default behavior will compare closest rotation on them all to pick one + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bMatchRotation; + + // If true then the hand socket will not be considered for search operations + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bDisabled; + + // Snap distance to use if you want to override the defaults. + // Will be ignored if == 0.0f or bAlwaysInRange is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + float OverrideDistance; + + // If true we are expected to have a list of custom deltas for bones to overlay onto our base pose + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + bool bUseCustomPoseDeltas; + + // Custom rotations that are added on top of an animations bone rotation to make a final transform + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + TArray CustomPoseDeltas; + + // Primary hand animation, for both hands if they share animations, right hand if they don't + // If using a custom pose delta this is expected to be the base pose + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + TObjectPtr HandTargetAnimation; + + // Scale to apply when mirroring the hand, adjust to visualize your off hand correctly + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + FVector MirroredScale; + +#if WITH_EDITORONLY_DATA + // If true we will attempt to only show editing widgets for bones matching the _l or _r postfixes + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + bool bFilterBonesByPostfix; + + // The postfix to filter by + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + FString FilterPostfix; + + // An array of bones to skip when looking to edit deltas, can help clean up the interaction if you have extra bones in the heirarchy + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + TArray BonesToSkip; + + FTransform GetBoneTransformAtTime(UAnimSequence* MyAnimSequence, /*float AnimTime,*/ int BoneIdx, FName BoneName, bool bUseRawDataOnly); +#endif + + // Returns the base target animation of the hand (if there is one) + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + UAnimSequence* GetTargetAnimation(); + + /** + * Returns the target animation of the hand blended with the delta rotations if there are any + * @param PoseSnapShot - Snapshot generated by this function + * @param TargetMesh - Targetmesh to check the skeleton of + * @param bSkipRootBone - If true we will skip the root bone (IE: Hand_r) and only apply the children poses (Full body) + * @param bFlipHand - If true we will mirror the pose, this is primarily to apply to a left hand from a right + */ + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + bool GetBlendedPoseSnapShot(FPoseSnapshot& PoseSnapShot, USkeletalMeshComponent* TargetMesh = nullptr, bool bSkipRootBone = false, bool bFlipHand = false); + + /** + * Converts an animation sequence into a pose snapshot + * @param InAnimationSequence - Sequence to convert to a pose snapshot + * @param OutPoseSnapShot - Snapshot returned by this function + * @param TargetMesh - Targetmesh to check the skeleton of + * @param bSkipRootBone - If true we will skip the root bone (IE: Hand_r) and only apply the children poses (Full body) + * @param bFlipHand - If true we will mirror the pose, this is primarily to apply to a left hand from a right + */ + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data", meta = (bIgnoreSelf = "true")) + static bool GetAnimationSequenceAsPoseSnapShot(UAnimSequence * InAnimationSequence, FPoseSnapshot& OutPoseSnapShot, USkeletalMeshComponent* TargetMesh = nullptr, bool bSkipRootBone = false, bool bFlipHand = false); + + // Returns the target relative transform of the hand + //UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + FTransform GetHandRelativePlacement(); + + inline void MirrorHandTransform(FTransform& ReturnTrans, FTransform& relTrans) + { + if (bOnlyFlipRotation) + { + ReturnTrans.SetTranslation(ReturnTrans.GetTranslation() - relTrans.GetTranslation()); + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetCrossAxis()); + ReturnTrans.SetTranslation(ReturnTrans.GetTranslation() + relTrans.GetTranslation()); + } + else + { + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetCrossAxis()); + } + } + + inline TEnumAsByte GetAsEAxis(TEnumAsByte InAxis) + { + switch (InAxis) + { + case EVRAxis::X: + { + return EAxis::X; + }break; + case EVRAxis::Y: + { + return EAxis::Y; + }break; + case EVRAxis::Z: + { + return EAxis::Z; + }break; + } + + return EAxis::X; + } + + + inline FVector GetMirrorVector() + { + switch (MirrorAxis) + { + case EVRAxis::Y: + { + return FVector::RightVector; + }break; + case EVRAxis::Z: + { + return FVector::UpVector; + }break; + case EVRAxis::X: + default: + { + return FVector::ForwardVector; + }break; + } + } + + inline FVector GetFlipVector() + { + switch (FlipAxis) + { + case EVRAxis::Y: + { + return FVector::RightVector; + }break; + case EVRAxis::Z: + { + return FVector::UpVector; + }break; + case EVRAxis::X: + default: + { + return FVector::ForwardVector; + }break; + } + } + + inline TEnumAsByte GetCrossAxis() + { + // Checking against the sign now to avoid possible mobile precision issues + FVector SignVec = MirroredScale.GetSignVector(); + + if (SignVec.X < 0) + { + return EAxis::X; + } + else if (SignVec.Z < 0) + { + return EAxis::Z; + } + else if (SignVec.Y < 0) + { + return EAxis::Y; + } + + return GetAsEAxis(FlipAxis); + + /*if (FlipAxis == EVRAxis::Y) + { + return EAxis::Z; + } + else if (FlipAxis == EVRAxis::Z) + { + return EAxis::X; + } + else if (FlipAxis == EVRAxis::X) + { + return EAxis::Y; + }*/ + + //return EAxis::None; + } + // Returns the target relative transform of the hand to the gripped object + // If you want the transform mirrored you need to pass in which hand is requesting the information + // If UseParentScale is true then we will scale the value by the parent scale (generally only for when not using absolute hand scale) + // If UseMirrorScale is true then we will mirror the scale on the hand by the hand sockets mirror scale when appropriate (not for fully body!) + // if UseMirrorScale is false than the resulting transform will not have mirroring scale added so you may have to break the transform. + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + FTransform GetMeshRelativeTransform(bool bIsRightHand, bool bUseParentScale = false, bool bUseMirrorScale = false); + + // Returns the defined hand socket component (if it exists, you need to valid check the return! + // If it is a valid return you can then cast to your projects base socket class and handle whatever logic you want + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + static UHandSocketComponent* GetHandSocketComponentFromObject(UObject* ObjectToCheck, FName SocketName); + + virtual FTransform GetHandSocketTransform(UGripMotionControllerComponent* QueryController, bool bIgnoreOnlySnapMesh = false); + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif +#if WITH_EDITORONLY_DATA + static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + void PoseVisualizationToAnimation(bool bForceRefresh = false); + bool bTickedPose; + + UPROPERTY() + bool bDecoupled; + +#endif + virtual void Serialize(FArchive& Ar) override; + virtual void OnRegister() override; + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + /** mesh component to indicate hand placement */ +#if WITH_EDITORONLY_DATA + + UPROPERTY() + TObjectPtr HandVisualizerComponent; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Transient, Category = "Hand Visualization") + TObjectPtr VisualizationMesh; + + // If we should show the visualization mesh + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bShowVisualizationMesh; + + // Show the visualization mirrored + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bMirrorVisualizationMesh; + + // If we should show the grip range of this socket (shows text if always in range) + // If override distance is zero then it attempts to infer the value from the parent architecture + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bShowRangeVisualization; + + void PositionVisualizationMesh(); + void HideVisualizationMesh(); + +#endif + +#if WITH_EDITORONLY_DATA + // Material to apply to the hand + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Visualization") + TObjectPtr HandPreviewMaterial; + +#endif +}; + +UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType) +class VREXPANSIONPLUGIN_API UHandSocketAnimInstance : public UAnimInstance +{ + GENERATED_BODY() + +public: + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, transient, Category = "Socket Data") + TObjectPtr OwningSocket; + + virtual void NativeInitializeAnimation() override + { + Super::NativeInitializeAnimation(); + + OwningSocket = Cast(GetOwningComponent()->GetAttachParent()); + } +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h new file mode 100644 index 0000000..5b06704 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h @@ -0,0 +1,189 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/StaticMeshComponent.h" +#include "VRInteractibleFunctionLibrary.h" +#include "VRButtonComponent.generated.h" + +/** +* +*/ + +// VR Button Types +UENUM(Blueprintable) +enum class EVRButtonType : uint8 +{ + Btn_Press, + Btn_Toggle_Return, + Btn_Toggle_Stay +}; + +// VR Button SyncOptions +UENUM(Blueprintable) +enum class EVRStateChangeAuthorityType : uint8 +{ + /* Button state can be changed on all connections */ + CanChangeState_All, + /* Button state can be changed only on the server */ + CanChangeState_Server, + /* Button state can be changed only on the owner of the interacting primitive */ + CanChangeState_Owner +}; + +/** Delegate for notification when the button state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FVRButtonStateChangedSignature, bool, ButtonState, AActor *, InteractingActor, UPrimitiveComponent *, InteractingComponent); + +/** Delegate for notification when the begins a new interaction. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRButtonStartedInteractionSignature, AActor *, InteractingActor, UPrimitiveComponent *, InteractingComponent); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRButtonComponent : public UStaticMeshComponent +{ + GENERATED_BODY() + +public: + UVRButtonComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRButtonComponent(); + + UFUNCTION() + void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + UFUNCTION() + void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UFUNCTION(BlueprintPure, Category = "VRButtonComponent") + bool IsButtonInUse(); + + // Should be called after the button is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void ResetInitialButtonLocation(); + + // Sets the button state outside of interaction, bSnapIntoPosition is for Toggle_Stay mode, it will lerp into the new position if this is false + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void SetButtonState(bool bNewButtonState, bool bCallButtonChangedEvent = true, bool bSnapIntoPosition = false); + + // Resets the button to its resting location (mostly for Toggle_Stay) + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void SetButtonToRestingPosition(bool bLerpToPosition = false); + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStateChangedSignature OnButtonStateChanged; + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button State Changed")) + void ReceiveButtonStateChanged(bool bCurButtonState, AActor * LastInteractingActor, UPrimitiveComponent * InteractingComponent); + + // On Button beginning interaction (may spam a bit depending on if overlap is jittering) + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStartedInteractionSignature OnButtonBeginInteraction; + + // On Button ending interaction (may spam a bit depending on if overlap is jittering) + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStartedInteractionSignature OnButtonEndInteraction; + + // On Button beginning interaction (may spam a bit depending on if overlap is jittering) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button Started Interaction")) + void ReceiveButtonBeginInteraction(AActor * InteractingActor, UPrimitiveComponent * InteractingComponent); + + // On Button ending interaction (may spam a bit depending on if overlap is jittering) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button Ended Interaction")) + void ReceiveButtonEndInteraction(AActor * LastInteractingActor, UPrimitiveComponent * LastInteractingComponent); + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr LocalInteractingComponent; + + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr LocalLastInteractingActor; + + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr LocalLastInteractingComponent; + + // Whether the button is enabled or not (can be interacted with) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + bool bIsEnabled; + + // Current state of the button, writable to set initial value + UPROPERTY(EditAnywhere,BlueprintReadWrite, Replicated, Category = "VRButtonComponent") + bool bButtonState; + + // Who is allowed to change the button state + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "VRButtonComponent|Replication") + EVRStateChangeAuthorityType StateChangeAuthorityType; + + // Speed that the button de-presses when no longer interacted with + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float DepressSpeed; + + // Distance that the button depresses + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float DepressDistance; + + // Type of button this is + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + EVRButtonType ButtonType; + + // Negative on this axis is the depress direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + EVRInteractibleAxis ButtonAxis; + + // Depth at which the button engages (switches) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float ButtonEngageDepth; + + // Minimum time before the button can be switched again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float MinTimeBetweenEngaging; + + // Skips filtering overlaps on the button and lets you manage it yourself, this is the alternative to overriding IsValidOverlap + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + bool bSkipOverlapFiltering; + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRButtonComponent") + bool IsValidOverlap(UPrimitiveComponent * OverlapComponent); + + // Sets the Last interacting actor variable + void SetLastInteractingActor(); + + virtual FVector GetTargetRelativeLocation(); + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRButtonComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + SetButtonToRestingPosition(); + } + +protected: + + // Control variables + FVector InitialLocation; + bool bToggledThisTouch; + FVector InitialComponentLoc; + float LastToggleTime; + + float GetAxisValue(FVector CheckLocation); + + FVector SetAxisValue(float SetValue); + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h new file mode 100644 index 0000000..8c510bb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h @@ -0,0 +1,307 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "VRGripInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "VRDialComponent.generated.h" + +class UGripMotionControllerComponent; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRDialStateChangedSignature, float, DialMilestoneAngle); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRDialFinishedLerpingSignature); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRDialComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRDialComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRDialComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRDialComponent") + FVRDialStateChangedSignature OnDialHitSnapAngle; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Dial Hit Snap Angle")) + void ReceiveDialHitSnapAngle(float DialMilestoneAngle); + + // If true the dial will lerp back to zero on release + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + bool bLerpBackOnRelease; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + bool bSendDialEventsDuringLerp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + float DialReturnSpeed; + + UPROPERTY(BlueprintReadOnly, Category = "VRDialComponent|Lerping") + bool bIsLerping; + + UPROPERTY(BlueprintAssignable, Category = "VRDialComponent|Lerping") + FVRDialFinishedLerpingSignature OnDialFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Dial Finished Lerping")) + void ReceiveDialFinishedLerping(); + + UPROPERTY(BlueprintReadOnly, Category = "VRDialComponent") + float CurrentDialAngle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent")//, meta = (ClampMin = "0.0", ClampMax = "360.0", UIMin = "0.0", UIMax = "360.0")) + float ClockwiseMaximumDialAngle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent")//, meta = (ClampMin = "0.0", ClampMax = "360.0", UIMin = "0.0", UIMax = "360.0")) + float CClockwiseMaximumDialAngle; + + // If true then the dial can "roll over" past 360/0 degrees in a direction + // Allowing unlimited dial angle values + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bUseRollover; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUsesAngleSnap; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUseSnapAngleList; + + // Optional list of snap angles for the dial + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "bDialUseSnapAngleList")) + TArray DialSnapAngleList; + + // Angle that the dial snaps to on release and when within the threshold distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "!bDialUseSnapAngleList", ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float SnapAngleIncrement; + + // Threshold distance that when within the dial will stay snapped to its closest snap increment + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "!bDialUseSnapAngleList", ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float SnapAngleThreshold; + + // Scales rotational input to speed up or slow down the rotation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float RotationScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + EVRInteractibleAxis DialRotationAxis; + + // If true then the dial will directly sample the hands rotation instead of using its movement around it. + // This is good for roll specific dials but is fairly bad elsewhere. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUseDirectHandRotation; + + float LastGripRot; + float InitialGripRot; + float InitialRotBackEnd; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "bDialUseDirectHandRotation")) + EVRInteractibleAxis InteractorRotationAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRDialComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + CalculateDialProgress(); + } + + void CalculateDialProgress(); + + //FTransform InitialRelativeTransform; + FVector InitialInteractorLocation; + FVector InitialDropLocation; + float CurRotBackEnd; + FRotator LastRotation; + float LastSnapAngle; + + // Should be called after the dial is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRDialComponent") + void ResetInitialDialLocation(); + + // Can be called to recalculate the dial angle after you move it if you want different values + UFUNCTION(BlueprintCallable, Category = "VRDialComponent") + void AddDialAngle(float DialAngleDelta, bool bCallEvents = false, bool bSkipSettingRot = false); + + // Directly sets the dial angle, still obeys maximum limits and snapping though + UFUNCTION(BlueprintCallable, Category = "VRDialComponent") + void SetDialAngle(float DialAngle, bool bCallEvents = false); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray& CurHoldingControllers, bool & bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray & ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h new file mode 100644 index 0000000..9e58d44 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h @@ -0,0 +1,303 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +//#include "UObject/ObjectMacros.h" +//#include "Engine/EngineTypes.h" +//#include "UObject/ScriptInterface.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Components/SceneComponent.h" +#include "GameplayTagContainer.h" + +#include "VRInteractibleFunctionLibrary.generated.h" + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(VRInteractibleFunctionLibraryLog, Log, All); + +// Declares our interactible axis's +UENUM(Blueprintable) +enum class EVRInteractibleAxis : uint8 +{ + Axis_X, + Axis_Y, + Axis_Z +}; + +// A data structure to hold important interactible data +// Should be init'd in Beginplay with BeginPlayInit as well as OnGrip with OnGripInit. +// Works in "static space", it records the original relative transform of the interactible on begin play +// so that calculations on the actual component can be done based off of it. +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRInteractibleBaseData +{ + GENERATED_BODY() +public: + + // Our initial relative transform to our parent "static space" - Set in BeginPlayInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FTransform InitialRelativeTransform; + + // Initial location in "static space" of the interactor on grip - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialInteractorLocation; + + // Initial location of the interactible in the "static space" - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialGripLoc; + + // Initial location on the interactible of the grip, used for drop calculations - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialDropLocation; + + // The initial transform in relative space of the grip to us - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FTransform ReversedRelativeTransform; + + FBPVRInteractibleBaseData() + { + InitialInteractorLocation = FVector::ZeroVector; + InitialGripLoc = FVector::ZeroVector; + InitialDropLocation = FVector::ZeroVector; + } +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UVRInteractibleFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + static float GetAtan2Angle(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation, float OptionalInitialRotation = 0.0f) + { + switch (AxisToCalc) + { + case EVRInteractibleAxis::Axis_X: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.Z)) - OptionalInitialRotation; + }break; + case EVRInteractibleAxis::Axis_Y: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Z, CurInteractorLocation.X)) - OptionalInitialRotation; + }break; + case EVRInteractibleAxis::Axis_Z: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.X)) - OptionalInitialRotation; + }break; + default: + {}break; + } + + return 0.0f; + } + + static float GetDeltaAngleFromTransforms(EVRInteractibleAxis RotAxis, FTransform & InitialRelativeTransform, FTransform &CurrentRelativeTransform) + { + return GetDeltaAngle(RotAxis, (CurrentRelativeTransform.GetRelativeTransform(InitialRelativeTransform).GetRotation()).GetNormalized()); + } + + static float GetDeltaAngle(EVRInteractibleAxis RotAxis, FQuat DeltaQuat) + { + FVector Axis; + float Angle; + DeltaQuat.ToAxisAndAngle(Axis, Angle); + + if (RotAxis == EVRInteractibleAxis::Axis_Z) + return FRotator::NormalizeAxis(FMath::RadiansToDegrees(Angle)) * (FMath::Sign(GetAxisValue(RotAxis, Axis))); + else + return FRotator::NormalizeAxis(FMath::RadiansToDegrees(Angle)) * (-FMath::Sign(GetAxisValue(RotAxis, Axis))); + } + + static float GetAxisValue(EVRInteractibleAxis RotAxis, FRotator CheckRotation) + { + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckRotation.Roll; break; + case EVRInteractibleAxis::Axis_Y: + return CheckRotation.Pitch; break; + case EVRInteractibleAxis::Axis_Z: + return CheckRotation.Yaw; break; + default:return 0.0f; break; + } + } + + static float GetAxisValue(EVRInteractibleAxis RotAxis, FVector CheckAxis) + { + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckAxis.X; break; + case EVRInteractibleAxis::Axis_Y: + return CheckAxis.Y; break; + case EVRInteractibleAxis::Axis_Z: + return CheckAxis.Z; break; + default:return 0.0f; break; + } + } + + static FVector SetAxisValueVec(EVRInteractibleAxis RotAxis, float SetValue) + { + FVector vec = FVector::ZeroVector; + + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.X = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Y = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Z = SetValue; break; + default:break; + } + + return vec; + } + + static FRotator SetAxisValueRot(EVRInteractibleAxis RotAxis, float SetValue) + { + FRotator vec = FRotator::ZeroRotator; + + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.Roll = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Pitch = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Yaw = SetValue; break; + default:break; + } + + return vec; + } + + static FRotator SetAxisValueRot(EVRInteractibleAxis RotAxis, float SetValue, FRotator Var) + { + FRotator vec = Var; + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.Roll = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Pitch = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Yaw = SetValue; break; + default:break; + } + + return vec; + } + + // Get current parent transform + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static FTransform Interactible_GetCurrentParentTransform(USceneComponent * SceneComponentToCheck) + { + if (SceneComponentToCheck) + { + if (USceneComponent * AttachParent = SceneComponentToCheck->GetAttachParent()) + { + return AttachParent->GetComponentTransform(); + } + } + + return FTransform::Identity; + } + + // Get current relative transform (original transform we were at on grip for the current parent transform) + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static FTransform Interactible_GetCurrentRelativeTransform(USceneComponent * SceneComponentToCheck, UPARAM(ref)FBPVRInteractibleBaseData & BaseData) + { + FTransform ParentTransform = FTransform::Identity; + if (SceneComponentToCheck) + { + if (USceneComponent * AttachParent = SceneComponentToCheck->GetAttachParent()) + { + // during grip there is no parent so we do this, might as well do it anyway for lerping as well + ParentTransform = AttachParent->GetComponentTransform(); + } + } + + return BaseData.InitialRelativeTransform * ParentTransform; + } + + // Inits the initial relative transform of an interactible on begin play + UFUNCTION(BlueprintCallable, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static void Interactible_BeginPlayInit(USceneComponent * InteractibleComp, UPARAM(ref) FBPVRInteractibleBaseData & BaseDataToInit) + { + if (!InteractibleComp) + return; + + BaseDataToInit.InitialRelativeTransform = InteractibleComp->GetRelativeTransform(); + } + + // Inits the calculated values of a VR Interactible Base Data Structure on a grip event + UFUNCTION(BlueprintCallable, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static void Interactible_OnGripInit(USceneComponent * InteractibleComp, UPARAM(ref) FBPActorGripInformation& GripInformation, UPARAM(ref) FBPVRInteractibleBaseData & BaseDataToInit) + { + if (!InteractibleComp) + return; + + BaseDataToInit.ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + BaseDataToInit.InitialDropLocation = BaseDataToInit.ReversedRelativeTransform.GetTranslation(); // Technically a duplicate, but will be more clear + + FTransform RelativeToGripTransform = BaseDataToInit.ReversedRelativeTransform * InteractibleComp->GetComponentTransform(); + FTransform CurrentRelativeTransform = BaseDataToInit.InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(InteractibleComp); + BaseDataToInit.InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + + BaseDataToInit.InitialGripLoc = BaseDataToInit.InitialRelativeTransform.InverseTransformPosition(InteractibleComp->GetRelativeLocation()); + } + + // Returns (in degrees) the angle around the axis of a location + // Expects the CurInteractorLocation to be in relative space already + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetAngleAroundAxis(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation) + { + float ReturnAxis = 0.0f; + + switch (AxisToCalc) + { + case EVRInteractibleAxis::Axis_X: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.Z)); + }break; + case EVRInteractibleAxis::Axis_Y: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Z, CurInteractorLocation.X)); + }break; + case EVRInteractibleAxis::Axis_Z: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.X)); + }break; + default: + {}break; + } + + return ReturnAxis; + } + + // Returns (in degrees) the delta rotation from the initial angle at grip to the current interactor angle around the axis + // Expects CurInteractorLocation to be in relative space already + // You can add this to an initial rotation and clamp the result to rotate over time based on hand position + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetAngleAroundAxisDelta(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation, float InitialAngle) + { + return FRotator::NormalizeAxis(Interactible_GetAngleAroundAxis(AxisToCalc, CurInteractorLocation) - InitialAngle); + } + + + // Returns a value that is snapped to the given settings, taking into account the threshold and increment + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetThresholdSnappedValue(float ValueToSnap, float SnapIncrement, float SnapThreshold) + { + if (SnapIncrement > 0.f && FMath::Fmod(ValueToSnap, SnapIncrement) <= FMath::Min(SnapIncrement, SnapThreshold)) + { + return FMath::GridSnap(ValueToSnap, SnapIncrement); + } + + return ValueToSnap; + } + +}; + + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h new file mode 100644 index 0000000..54e457b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h @@ -0,0 +1,409 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "VRGripInterface.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "Components/StaticMeshComponent.h" +#include "VRLeverComponent.generated.h" + +class UGripMotionControllerComponent; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverAxis : uint8 +{ + /* Rotates only towards the X Axis */ + Axis_X, + /* Rotates only towards the Y Axis */ + Axis_Y, + /* Rotates only towards the Z Axis */ + Axis_Z, + /* Rotates freely on the XY Axis' */ + Axis_XY, + /* Acts like a flight stick, with AllCurrentLeverAngles being the positive / negative of the current full angle (yaw based on initial grip delta) */ + FlightStick_XY, +}; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverEventType : uint8 +{ + LeverPositive, + LeverNegative +}; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverReturnType : uint8 +{ + /** Stays in place on drop */ + Stay, + + /** Returns to zero on drop (lerps) */ + ReturnToZero, + + /** Lerps to closest max (only works with X/Y/Z axis levers) */ + LerpToMax, + + /** Lerps to closest max if over the toggle threshold (only works with X/Y/Z axis levers) */ + LerpToMaxIfOverThreshold, + + /** Retains momentum on release (only works with X/Y/Z axis levers) */ + RetainMomentum +}; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FVRLeverStateChangedSignature, bool, LeverStatus, EVRInteractibleLeverEventType, LeverStatusType, float, LeverAngleAtTime, float, FullLeverAngleAtTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRLeverFinishedLerpingSignature, float, FinalAngle); + +/** +* A Lever component, can act like a lever, door, wheel, joystick. +* If set to replicates it will replicate its values to the clients. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRLeverComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRLeverComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRLeverComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRLeverComponent") + FVRLeverStateChangedSignature OnLeverStateChanged; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Lever State Changed")) + void ReceiveLeverStateChanged(bool LeverStatus, EVRInteractibleLeverEventType LeverStatusType, float LeverAngleAtTime, float FullLeverAngleAttime); + + UPROPERTY(BlueprintAssignable, Category = "VRLeverComponent") + FVRLeverFinishedLerpingSignature OnLeverFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Lever Finished Lerping")) + void ReceiveLeverFinishedLerping(float LeverFinalAngle); + + // Primary axis angle only + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + float CurrentLeverAngle; + + // Writes out all current angles to this rotator, useful mostly for XY and Flight stick modes + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + FRotator AllCurrentLeverAngles; + + // Bearing Direction, for X/Y is their signed direction, for XY mode it is an actual 2D directional vector + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + FVector CurrentLeverForwardVector; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bUngripAtTargetRotation; + + // Rotation axis to use, XY is combined X and Y, only LerpToZero and PositiveLimits work with this mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + EVRInteractibleLeverAxis LeverRotationAxis; + + // The percentage of the angle at witch the lever will toggle + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.01", ClampMax = "1.0", UIMin = "0.01", UIMax = "1.0")) + float LeverTogglePercentage; + + // The max angle of the lever in the positive direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float LeverLimitPositive; + + // The max angle of the lever in the negative direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float LeverLimitNegative; + + // The max angle of the flightsticks yaw in either direction off of 0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float FlightStickYawLimit; + + // If true then this lever is locked in place until unlocked again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bIsLocked; + + // If true then this lever will auto drop even when locked + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bAutoDropWhenLocked; + + // Sets if the lever is locked or not + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetIsLocked(bool bNewLockedState); + + // Checks and applies auto drop if we need too + bool CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + EVRInteractibleLeverReturnType LeverReturnTypeWhenReleased; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bSendLeverEventsDuringLerp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + float LeverReturnSpeed; + + // If true then we will blend the values of the XY axis' by the AngleThreshold, lowering Pitch/Yaw influence based on how far from leaning into the axis that the lever is + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings") + bool bBlendAxisValuesByAngleThreshold; + + // The angle threshold to blend around, default of 90.0 blends 0.0 to 1.0 smoothly across entire sweep + // A value of 45 would blend it to 0 halfway rotated to the other axis, while 180 would still leave half the influence when fully rotated out of facing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings", meta = (ClampMin = "1.0", ClampMax = "360.0", UIMin = "1.0", UIMax = "360.0")) + float AngleThreshold; + + // Number of frames to average momentum across for the release momentum (avoids quick waggles) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0", ClampMax = "12", UIMin = "0", UIMax = "12")) + int FramesToAverage; + + // Units in degrees per second to slow a momentum lerp down + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "180", UIMin = "0.0", UIMax = "180.0")) + float LeverMomentumFriction; + + // % of elasticity on reaching the end 0 - 1.0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float LeverRestitution; + + // Maximum momentum of the lever in degrees per second + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", UIMin = "0.0")) + float MaxLeverMomentum; + + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + bool bIsLerping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Full precision current angle + float FullCurrentAngle; + + float LastDeltaAngle; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRLeverComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + ReCalculateCurrentAngle(); + } + + // Flight stick variables + FTransform InteractorOffsetTransform; + + FVector InitialInteractorLocation; + FVector InitialInteractorDropLocation; + float InitialGripRot; + float RotAtGrab; + FQuat qRotAtGrab; + bool bLeverState; + bool bIsInFirstTick; + + // For momentum retention + float MomentumAtDrop; + float LastLeverAngle; + + float CalcAngle(EVRInteractibleLeverAxis AxisToCalc, FVector CurInteractorLocation, bool bSkipLimits = false); + + void LerpAxis(float CurrentAngle, float DeltaTime); + + void CalculateCurrentAngle(FTransform & CurrentTransform); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Stiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Damping; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + TWeakObjectPtr ParentComponent; + + // Should be called after the lever is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void ResetInitialLeverLocation(bool bAllowThrowingEvents = true); + + /** + * Sets the angle of the lever forcefully + * @param NewAngle The new angle to use, for single axis levers the sign of the angle will be used + * @param DualAxisForwardVector Only used with dual axis levers, you need to define the forward axis for the angle to apply too + */ + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void SetLeverAngle(float NewAngle, FVector DualAxisForwardVector, bool bAllowThrowingEvents = true); + + // ReCalculates the current angle, sets it on the back end, and returns it + // If allow throwing events is true then it will trigger the callbacks for state changes as well + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + float ReCalculateCurrentAngle(bool bAllowThrowingEvents = true); + + void ProccessCurrentState(bool bWasLerping = false, bool bThrowEvents = true, bool bCheckAutoDrop = true); + + virtual void OnUnregister() override; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray& CurHoldingControllers, bool& bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h new file mode 100644 index 0000000..5655040 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h @@ -0,0 +1,254 @@ + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "VRGripInterface.h" +#include "VRMountComponent.generated.h" + +class UGripMotionControllerComponent; + + +UENUM(Blueprintable) +enum class EVRInteractibleMountAxis : uint8 +{ + /** Limit Rotation to Yaw and Roll */ + Axis_XZ +}; + +// A mounted lever/interactible implementation - Created by SpaceHarry - Merged into the plugin 01/29/2018 +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRMountComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRMountComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRMountComponent(); + + + // Rotation axis to use, XY is combined X and Y, only LerpToZero and PositiveLimits work with this mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + EVRInteractibleMountAxis MountRotationAxis; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + FTransform InitialRelativeTransform; + FVector InitialInteractorLocation; + FVector InitialInteractorDropLocation; + float InitialGripRot; + FQuat qRotAtGrab; + + // If the mount is swirling around a 90 degree pitch increase this number by 0.1 steps. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + float FlipingZone; + + // If the mount feels lagging behind in yaw at some point after 90 degree pitch increase this number by 0.1 steps + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + float FlipReajustYawSpeed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + bool GrippedOnBack; + + bool bIsInsideFrontFlipingZone; + bool bIsInsideBackFlipZone; + FVector CurInterpGripLoc; + + float TwistDiff; + FVector InitialGripToForwardVec; + FVector InitialForwardVector; + FVector EntryUpXYNeg; + FVector EntryUpVec; + FVector EntryRightVec; + + bool bFirstEntryToHalfFlipZone; + bool bLerpingOutOfFlipZone; + bool bIsFlipped; + + FPlane FlipPlane; + FPlane ForwardPullPlane; + FVector LastPointOnForwardPlane; + FVector CurPointOnForwardPlane; + + float LerpOutAlpha; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Stiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Damping; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Should be called after the Mount is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRMountComponent") + void ResetInitialMountLocation() + { + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + } + + virtual void OnUnregister() override;; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray& CurHoldingControllers, bool& bCurIsHeld) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + +protected: + + + +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h new file mode 100644 index 0000000..f0aaa1e --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h @@ -0,0 +1,406 @@ +// Fill out your copyright notice in the Description page of Project Settings. +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "VRGripInterface.h" +#include "GameplayTagAssetInterface.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "Components/StaticMeshComponent.h" +#include "VRSliderComponent.generated.h" + +class UGripMotionControllerComponent; +class USplineComponent; + +UENUM(Blueprintable) +enum class EVRInteractibleSliderLerpType : uint8 +{ + Lerp_None, + Lerp_Interp, + Lerp_InterpConstantTo +}; + +UENUM(Blueprintable) +enum class EVRInteractibleSliderDropBehavior : uint8 +{ + /** Stays in place on drop */ + Stay, + + /** Retains momentum on release*/ + RetainMomentum +}; + +/** Delegate for notification when the slider state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRSliderHitPointSignature, float, SliderProgressPoint); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRSliderFinishedLerpingSignature, float, FinalProgress); + +/** +* A slider component, can act like a scroll bar, or gun bolt, or spline following component +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRSliderComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRSliderComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRSliderComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRSliderComponent") + FVRSliderHitPointSignature OnSliderHitPoint; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Slider State Changed")) + void ReceiveSliderHitPoint(float SliderProgressPoint); + + UPROPERTY(BlueprintAssignable, Category = "VRSliderComponent") + FVRSliderFinishedLerpingSignature OnSliderFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Slider Finished Lerping")) + void ReceiveSliderFinishedLerping(float FinalProgress); + + // If true then this slider will only update in its tick event instead of normally using the controllers update event + // Keep in mind that you then must adjust the tick group in order to make sure it happens after the gripping controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bUpdateInTick; + bool bPassThrough; + + float LastSliderProgressState; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + FVector MaxSlideDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + FVector MinSlideDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + EVRInteractibleSliderDropBehavior SliderBehaviorWhenReleased; + + // Number of frames to average momentum across for the release momentum (avoids quick waggles) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0", ClampMax = "12", UIMin = "0", UIMax = "12")) + int FramesToAverage; + + // Units in % of total length per second to slow a momentum lerp down + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "0.0", UIMax = "10.0")) + float SliderMomentumFriction; + + // % of elasticity on reaching the end 0 - 1.0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SliderRestitution; + + // Maximum momentum of the slider in units of the total distance per second (0.0 - 1.0) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", UIMin = "0.0")) + float MaxSliderMomentum; + + UPROPERTY(BlueprintReadOnly, Category = "VRSliderComponent") + bool bIsLerping; + + // For momentum retention + FVector MomentumAtDrop; + FVector LastSliderProgress; + float SplineMomentumAtDrop; + float SplineLastSliderProgress; + + // Gets filled in with the current slider location progress + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "VRSliderComponent") + float CurrentSliderProgress; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bSlideDistanceIsInParentSpace; + + // If true then this slider is locked in place until unlocked again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bIsLocked; + + // If true then this slider will auto drop even when locked + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bAutoDropWhenLocked; + + // Sets if the slider is locked or not + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetIsLocked(bool bNewLockedState); + + // Uses the legacy slider logic that doesn't ABS the min and max values + // Retains compatibility with some older projects + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bUseLegacyLogic; + + // How far away from an event state before the slider allows throwing the same state again, default of 1.0 means it takes a full toggle + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float EventThrowThreshold; + bool bHitEventThreshold; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Set this to assign a spline component to the slider + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated/*Using = OnRep_SplineComponentToFollow*/, Category = "VRSliderComponent") + TObjectPtr SplineComponentToFollow; + + /*UFUNCTION() + virtual void OnRep_SplineComponentToFollow() + { + CalculateSliderProgress(); + }*/ + + // Where the slider should follow the rotation and scale of the spline as well + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bFollowSplineRotationAndScale; + + // Does not allow the slider to skip past nodes on the spline, it requires it to progress from node to node + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bEnforceSplineLinearity; + float LastInputKey; + float LerpedKey; + + // Type of lerp to use when following a spline + // For lerping I would suggest using ConstantTo in general as it will be the smoothest. + // Normal Interp will change speed based on distance, that may also have its uses. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + EVRInteractibleSliderLerpType SplineLerpType; + + // Lerp Value for the spline, when in InterpMode it is the speed of interpolation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (ClampMin = "0", UIMin = "0")) + float SplineLerpValue; + + // Uses snap increments to move between, not compatible with retain momentum. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bSliderUsesSnapPoints; + + // Portion of the slider that the slider snaps to on release and when within the threshold distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints", ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SnapIncrement; + + // Threshold distance that when within the slider will stay snapped to its current snap increment + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints", ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SnapThreshold; + + // If true then the slider progress will keep incrementing between snap points if outside of the threshold + // However events will not be thrown + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints") ) + bool bIncrementProgressBetweenSnapPoints; + + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRSliderComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + CalculateSliderProgress(); + } + + FVector InitialInteractorLocation; + FVector InitialGripLoc; + FVector InitialDropLocation; + + // Checks if we should throw some events + void CheckSliderProgress(); + + /* + Credit to: + https://github.com/Seurabimn + + Who had this function in an engine pull request: + https://github.com/EpicGames/UnrealEngine/pull/6646 + */ + float GetDistanceAlongSplineAtSplineInputKey(float InKey) const; + + + // Calculates the current slider progress + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + float CalculateSliderProgress(); + + // Forcefully sets the slider progress to the defined value + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void SetSliderProgress(float NewSliderProgress); + + // Should be called after the slider is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void ResetInitialSliderLocation(); + + // Sets the spline component to follow, if empty, just reinitializes the transform and removes the follow component + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void SetSplineComponentToFollow(USplineComponent * SplineToFollow); + + void ResetToParentSplineLocation(); + + void GetLerpedKey(float &ClosestKey, float DeltaTime); + float GetCurrentSliderProgress(FVector CurLocation, bool bUseKeyInstead = false, float CurKey = 0.f); + FVector ClampSlideVector(FVector ValueToClamp); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Checks and applies auto drop if we need too + bool CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray& CurHoldingControllers, bool& bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h new file mode 100644 index 0000000..4dacd14 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h @@ -0,0 +1,169 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "Tickable.h" +#include "BucketUpdateSubsystem.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPhysicsReplicationDelegate, void, Return); + + +DECLARE_DELEGATE_RetVal(bool, FBucketUpdateTickSignature); +DECLARE_DYNAMIC_DELEGATE(FDynamicBucketUpdateTickSignature); + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucketDrop +{ + GENERATED_BODY() +public: + FBucketUpdateTickSignature NativeCallback; + FDynamicBucketUpdateTickSignature DynamicCallback; + + FName FunctionName; + + bool ExecuteBoundCallback(); + bool IsBoundToObjectFunction(UObject * Obj, FName & FuncName); + bool IsBoundToObjectDelegate(FDynamicBucketUpdateTickSignature & DynEvent); + bool IsBoundToObject(UObject * Obj); + FUpdateBucketDrop(); + FUpdateBucketDrop(FDynamicBucketUpdateTickSignature & DynCallback); + FUpdateBucketDrop(UObject * Obj, FName FuncName); +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucket +{ + GENERATED_BODY() + +public: + + float nUpdateRate; + float nUpdateCount; + + TArray Callbacks; + + bool Update(float DeltaTime); + + FUpdateBucket() {} + + FUpdateBucket(uint32 UpdateHTZ) : + nUpdateRate(1.0f / UpdateHTZ), + nUpdateCount(0.0f) + { + } +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucketContainer +{ + GENERATED_BODY() +public: + + + bool bNeedsUpdate; + TMap ReplicationBuckets; + + void UpdateBuckets(float DeltaTime); + + bool AddBucketObject(uint32 UpdateHTZ, UObject* InObject, FName FunctionName); + bool AddBucketObject(uint32 UpdateHTZ, FDynamicBucketUpdateTickSignature &Delegate); + + /* + template + bool AddReplicatingObject(uint32 UpdateHTZ, classType* InObject, void(classType::* _Func)()) + { + } + */ + + bool RemoveBucketObject(UObject * ObjectToRemove, FName FunctionName); + bool RemoveBucketObject(FDynamicBucketUpdateTickSignature &DynEvent); + bool RemoveObjectFromAllBuckets(UObject * ObjectToRemove); + + bool IsObjectInBucket(UObject * ObjectToRemove); + bool IsObjectFunctionInBucket(UObject * ObjectToRemove, FName FunctionName); + bool IsObjectDelegateInBucket(FDynamicBucketUpdateTickSignature &DynEvent); + + FUpdateBucketContainer() + { + bNeedsUpdate = false; + }; + +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UBucketUpdateSubsystem : public UTickableWorldSubsystem +{ + GENERATED_BODY() + +public: + UBucketUpdateSubsystem() : + Super() + { + + } + + virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override + { + return WorldType == EWorldType::Game || WorldType == EWorldType::PIE; + // Not allowing for editor type as this is a replication subsystem + } + + //UPROPERTY() + FUpdateBucketContainer BucketContainer; + + // Adds an object to an update bucket with the set HTZ, calls the passed in UFUNCTION name + // If one of the bucket contains an entry with the function already then the existing one is removed and the new one is added + bool AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName); + + // Adds an object to an update bucket with the set HTZ, calls the passed in UFUNCTION name + // If one of the bucket contains an entry with the function already then the existing one is removed and the new one is added + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object to Bucket Updates", ScriptName = "AddObjectToBucket"), Category = "BucketUpdateSubsystem") + bool K2_AddObjectToBucket(int32 UpdateHTZ = 100, UObject* InObject = nullptr, FName FunctionName = NAME_None); + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object to Bucket Updates by Event", ScriptName = "AddBucketObjectEvent"), Category = "BucketUpdateSubsystem") + bool K2_AddObjectEventToBucket(UPARAM(DisplayName = "Event") FDynamicBucketUpdateTickSignature Delegate, int32 UpdateHTZ = 100); + + // Remove the entry in the bucket updates with the passed in UFUNCTION name + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From Bucket Updates By Function", ScriptName = "RemoveObjectFromBucketByFunction"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromBucketByFunctionName(UObject* InObject = nullptr, FName FunctionName = NAME_None); + + // Remove the entry in the bucket updates with the passed in UFUNCTION name + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From Bucket Updates By Event", ScriptName = "RemoveObjectFromBucketByEvent"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromBucketByEvent(UPARAM(DisplayName = "Event") FDynamicBucketUpdateTickSignature Delegate); + + // Removes ALL entries in the bucket update system with the specified object + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From All Bucket Updates", ScriptName = "RemoveObjectFromAllBuckets"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromAllBuckets(UObject* InObject = nullptr); + + // Returns if an update bucket contains an entry with the passed in function + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is Object In Bucket", ScriptName = "IsObjectInBucket"), Category = "BucketUpdateSubsystem") + bool IsObjectFunctionInBucket(UObject* InObject = nullptr, FName FunctionName = NAME_None); + + // Returns if an update bucket contains an entry with the passed in function + UFUNCTION(BlueprintPure, Category = "BucketUpdateSubsystem") + bool IsActive(); + + // FTickableGameObject functions + /** + * Function called every frame on this GripScript. Override this function to implement custom logic to be executed every frame. + * Only executes if bCanEverTick is true and bAllowTicking is true + * + * @param DeltaTime - The time since the last tick. + */ + + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override; + virtual UWorld* GetTickableGameObjectWorld() const override; + virtual bool IsTickableInEditor() const; + virtual bool IsTickableWhenPaused() const override; + virtual ETickableTickType GetTickableTickType() const; + virtual TStatId GetStatId() const override; + + // End tickable object information + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h new file mode 100644 index 0000000..5c64261 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h @@ -0,0 +1,255 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "TimerManager.h" +#include "Components/PrimitiveComponent.h" +//#include "Grippables/GrippablePhysicsReplication.h" + +// For async physics scene callback modifications +// Some of these can be moved out to the cpp +#include "Chaos/SimCallbackObject.h" +#include "Chaos/SimCallbackInput.h" +#include "Chaos/ParticleHandle.h" +//#include "Chaos/ContactModification.h" +//#include "PBDRigidsSolver.h" + + +#include "CollisionIgnoreSubsystem.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + + + +DECLARE_LOG_CATEGORY_EXTERN(VRE_CollisionIgnoreLog, Log, All); + + +USTRUCT() +struct FChaosParticlePair +{ + GENERATED_BODY() + + Chaos::TPBDRigidParticleHandle* ParticleHandle0; + Chaos::TPBDRigidParticleHandle* ParticleHandle1; + + FChaosParticlePair() + { + ParticleHandle0 = nullptr; + ParticleHandle1 = nullptr; + } + + FChaosParticlePair(Chaos::TPBDRigidParticleHandle* pH1, Chaos::TPBDRigidParticleHandle* pH2) + { + ParticleHandle0 = pH1; + ParticleHandle1 = pH2; + } + + FORCEINLINE bool operator==(const FChaosParticlePair& Other) const + { + return( + (ParticleHandle0 == Other.ParticleHandle0 || ParticleHandle0 == Other.ParticleHandle1) && + (ParticleHandle1 == Other.ParticleHandle1 || ParticleHandle1 == Other.ParticleHandle0) + ); + } +}; + +/* +* All input is const, non-const data goes in output. 'AsyncSimState' points to non-const sim state. +*/ +struct FSimCallbackInputVR : public Chaos::FSimCallbackInput +{ + virtual ~FSimCallbackInputVR() {} + void Reset() + { + ParticlePairs.Empty(); + } + + TArray ParticlePairs; + + bool bIsInitialized; +}; + +struct FSimCallbackNoOutputVR : public Chaos::FSimCallbackOutput +{ + void Reset() {} +}; + +class FCollisionIgnoreSubsystemAsyncCallback : public Chaos::TSimCallbackObject +{ + +private: + + virtual void OnPreSimulate_Internal() override + { + // Copy paired bodies here? + + // Actually use input data to input our paired bodies and copy over here + } + + /** + * Called once per simulation step. Allows user to modify contacts + * + * NOTE: you must explicitly request contact modification when registering the callback for this to be called + */ + virtual void OnContactModification_Internal(Chaos::FCollisionContactModifier& Modifier) override; + +}; + + +USTRUCT() +struct FCollisionPrimPair +{ + GENERATED_BODY() +public: + + UPROPERTY() + TObjectPtr Prim1; + UPROPERTY() + TObjectPtr Prim2; + + FCollisionPrimPair() + { + Prim1 = nullptr; + Prim2 = nullptr; + } + + FORCEINLINE bool operator==(const FCollisionPrimPair& Other) const + { + /*if (!Prim1.IsValid() || !Prim2.IsValid()) + return false; + + if (!Other.Prim1.IsValid() || !Other.Prim2.IsValid()) + return false;*/ + + return( + (Prim1.Get() == Other.Prim1.Get() || Prim1.Get() == Other.Prim2.Get()) && + (Prim2.Get() == Other.Prim1.Get() || Prim2.Get() == Other.Prim2.Get()) + ); + } + + friend uint32 GetTypeHash(const FCollisionPrimPair& InKey) + { + return GetTypeHash(InKey.Prim1) ^ GetTypeHash(InKey.Prim2); + } + +}; + +USTRUCT() +struct FCollisionIgnorePair +{ + GENERATED_BODY() +public: + + ///FCollisionPrimPair PrimitivePair; + FPhysicsActorHandle Actor1; + UPROPERTY() + FName BoneName1; + FPhysicsActorHandle Actor2; + UPROPERTY() + FName BoneName2; + + // Flip our elements to retain a default ordering in an array + void FlipElements() + { + FPhysicsActorHandle tH = Actor1; + Actor1 = Actor2; + Actor2 = tH; + + FName tN = BoneName1; + BoneName1 = BoneName2; + BoneName2 = tN; + } + + FORCEINLINE bool operator==(const FCollisionIgnorePair& Other) const + { + return ( + (BoneName1 == Other.BoneName1 || BoneName1 == Other.BoneName2) && + (BoneName2 == Other.BoneName2 || BoneName2 == Other.BoneName1) + ); + } + + FORCEINLINE bool operator==(const FName& Other) const + { + return (BoneName1 == Other || BoneName2 == Other); + } +}; + +USTRUCT() +struct FCollisionIgnorePairArray +{ + GENERATED_BODY() +public: + + UPROPERTY() + TArray PairArray; +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UCollisionIgnoreSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + + + UCollisionIgnoreSubsystem() : + Super() + { + ContactModifierCallback = nullptr; + } + + FCollisionIgnoreSubsystemAsyncCallback* ContactModifierCallback; + + void ConstructInput(); + + virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override + { + return WorldType == EWorldType::Game || WorldType == EWorldType::PIE; + // Not allowing for editor type as this is a replication subsystem + } + + /** Implement this for initialization of instances of the system */ + virtual void Initialize(FSubsystemCollectionBase& Collection) override + { + Super::Initialize(Collection); + } + + virtual void Deinitialize() override + { + Super::Deinitialize(); + + if (UpdateHandle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(UpdateHandle); + } + } + + UPROPERTY() + TMap CollisionTrackedPairs; + //TArray CollisionTrackedPairs; + + UPROPERTY() + TMap RemovedPairs; + //TArray RemovedPairs; + + // + void UpdateTimer(bool bChangesWereMade); + + UFUNCTION(Category = "Collision") + void CheckActiveFilters(); + + // #TODO implement this, though it should be rare + void InitiateIgnore(); + + void SetComponentCollisionIgnoreState(bool bIterateChildren1, bool bIterateChildren2, UPrimitiveComponent* Prim1, FName OptionalBoneName1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bIgnoreCollision, bool bCheckFilters = false); + void RemoveComponentCollisionIgnoreState(UPrimitiveComponent* Prim1); + bool IsComponentIgnoringCollision(UPrimitiveComponent* Prim1); + bool AreComponentsIgnoringCollisions(UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2); + bool HasCollisionIgnorePairs(); +private: + + FTimerHandle UpdateHandle; + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h new file mode 100644 index 0000000..0067180 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h @@ -0,0 +1,132 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GripMotionControllerComponent.h" +//#include "Engine/Engine.h" +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/SphereComponent.h" +#include "Engine/ActorChannel.h" +#include "OptionalRepSkeletalMeshActor.generated.h" + +// Temp comp to avoid some engine issues, exists only until a bug fix happens +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UNoRepSphereComponent : public USphereComponent +{ + GENERATED_BODY() + +public: + UNoRepSphereComponent(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; +}; + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ + +USTRUCT() +struct FSkeletalMeshComponentEndPhysicsTickFunctionVR : public FTickFunction +{ + GENERATED_USTRUCT_BODY() + + UInversePhysicsSkeletalMeshComponent* TargetVR; + + virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; + + virtual FString DiagnosticMessage() override; + + virtual FName DiagnosticContext(bool bDetailed) override; + +}; +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithCopy = false + }; +}; + +// A base skeletal mesh component that has been added to temp correct an engine bug with inversed scale and physics +UCLASS(Blueprintable, meta = (ChildCanTick, BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UInversePhysicsSkeletalMeshComponent : public USkeletalMeshComponent +{ + GENERATED_BODY() + +public: + UInversePhysicsSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + +public: + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + // This is all overrides to fix the skeletal mesh inverse simulation bug + // WILL BE REMOVED LATER when the engine is fixed + FSkeletalMeshComponentEndPhysicsTickFunctionVR EndPhysicsTickFunctionVR; + friend struct FSkeletalMeshComponentEndPhysicsTickFunctionVR; + void EndPhysicsTickComponentVR(FSkeletalMeshComponentEndPhysicsTickFunctionVR& ThisTickFunction); + void BlendInPhysicsInternalVR(FTickFunction& ThisTickFunction); + void FinalizeAnimationUpdateVR(); + + // Weld Fix until epic fixes it #TODO: check back in on this and remove and the asset include when epic fixes it + virtual void GetWeldedBodies(TArray& OutWeldedBodies, TArray& OutLabels, bool bIncludingAutoWeld) override; + virtual FBodyInstance* GetBodyInstance(FName BoneName = NAME_None, bool bGetWelded = true, int32 Index = INDEX_NONE) const override; + + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override + { + // Get rid of inverse issues + FTransform newLocalToWorld = LocalToWorld; + newLocalToWorld.SetScale3D(newLocalToWorld.GetScale3D().GetAbs()); + + return Super::CalcBounds(newLocalToWorld); + } + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions") + FBoxSphereBounds GetLocalBounds() const + { + return this->GetCachedLocalBounds(); + } + + void PerformBlendPhysicsBonesVR(const TArray& InRequiredBones, TArray& InOutComponentSpaceTransforms, TArray& InOutBoneSpaceTransforms); + virtual void RegisterEndPhysicsTick(bool bRegister) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + // END INVERSED MESH FIX + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + +}; + +/** +* +* A class specifically for turning off default physics replication with a skeletal mesh and fixing an +* engine bug with inversed scale on skeletal meshes. Generally used for the physical hand setup. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AOptionalRepGrippableSkeletalMeshActor : public ASkeletalMeshActor +{ + GENERATED_BODY() + +public: + AOptionalRepGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer); + + // Skips the attachment replication + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bIgnoreAttachmentReplication; + + // Skips the physics replication + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bIgnorePhysicsReplication; + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_ReplicateMovement() override; + virtual void PostNetReceivePhysicState() override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h new file mode 100644 index 0000000..7b23c6c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h @@ -0,0 +1,398 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "VRBaseCharacter.h" +#include "GenericTeamAgentInterface.h" +#include "Perception/AISense.h" +#include "Perception/AISense_Sight.h" +#include "Perception/AISenseConfig.h" +#include "WorldCollision.h" +#include "Misc/MTAccessDetector.h" + +#include "VRAIPerceptionOverrides.generated.h" + +class IAISightTargetInterface; +class UAISense_Sight_VR; + +class FGameplayDebuggerCategory; +class UAIPerceptionComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogAIPerceptionVR, Warning, All); + +UCLASS(meta = (DisplayName = "AI Sight VR config")) +class VREXPANSIONPLUGIN_API UAISenseConfig_Sight_VR : public UAISenseConfig +{ + GENERATED_UCLASS_BODY() + +public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", NoClear, config) + TSubclassOf Implementation; + + /** Maximum sight distance to notice a target. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float SightRadius; + + /** Maximum sight distance to see target that has been already seen. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float LoseSightRadius; + + /** How far to the side AI can see, in degrees. Use SetPeripheralVisionAngle to change the value at runtime. + * The value represents the angle measured in relation to the forward vector, not the whole range. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0, UIMax = 180.0, ClampMax = 180.0, DisplayName = "PeripheralVisionHalfAngleDegrees")) + float PeripheralVisionAngleDegrees; + + /** */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config) + FAISenseAffiliationFilter DetectionByAffiliation; + + /** If not an InvalidRange (which is the default), we will always be able to see the target that has already been seen if they are within this range of their last seen location. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config) + float AutoSuccessRangeFromLastSeenLocation; + + /** Point of view move back distance for cone calculation. In conjunction with near clipping distance, this will act as a close by awareness and peripheral vision. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float PointOfViewBackwardOffset; + + /** Near clipping distance, to be used with point of view backward offset. Will act as a close by awareness and peripheral vision */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float NearClippingRadius; + + virtual TSubclassOf GetSenseImplementation() const override; + + +#if WITH_EDITOR + virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + +#if WITH_GAMEPLAY_DEBUGGER + virtual void DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const; +#endif // WITH_GAMEPLAY_DEBUGGER + +}; + + +namespace ESightPerceptionEventNameVR +{ + enum Type + { + Undefined, + GainedSight, + LostSight + }; +} + +USTRUCT() +struct VREXPANSIONPLUGIN_API FAISightEventVR +{ + GENERATED_USTRUCT_BODY() + + typedef UAISense_Sight_VR FSenseClass; + + float Age; + ESightPerceptionEventNameVR::Type EventType; + + UPROPERTY() + TObjectPtr SeenActor; + + UPROPERTY() + TObjectPtr Observer; + + FAISightEventVR() : SeenActor(nullptr), Observer(nullptr) {} + + FAISightEventVR(AActor* InSeenActor, AActor* InObserver, ESightPerceptionEventNameVR::Type InEventType) + : Age(0.f), EventType(InEventType), SeenActor(InSeenActor), Observer(InObserver) + { + } +}; + +struct FAISightTargetVR +{ + typedef uint32 FTargetId; + static const FTargetId InvalidTargetId; + + TWeakObjectPtr Target; + IAISightTargetInterface* SightTargetInterface; + FGenericTeamId TeamId; + FTargetId TargetId; + + FAISightTargetVR(AActor* InTarget = NULL, FGenericTeamId InTeamId = FGenericTeamId::NoTeam); + + FORCEINLINE FVector GetLocationSimple() const + { + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast(Target); + return Target.IsValid() ? (VRChar != nullptr ? VRChar->GetVRLocation_Inline() : Target->GetActorLocation()) : FVector::ZeroVector; + } + + FORCEINLINE const AActor* GetTargetActor() const { return Target.Get(); } +}; + +struct FAISightQueryVR +{ + FPerceptionListenerID ObserverId; + FAISightTargetVR::FTargetId TargetId; + + float Score; + float Importance; + + FVector LastSeenLocation; + + /** User data that can be used inside the IAISightTargetInterface::CanBeSeenFrom method to store a persistence state */ + mutable int32 UserData; + + union + { + /** + * We can share the memory for these values because they aren't used at the same time : + * - The FrameInfo is used when the query is queued for an update at a later frame. It stores the last time the + * query was processed so that we can prioritize it accordingly against the other queries + * - The TraceInfo is used when the query has requested a asynchronous trace and is waiting for the result. + * The engine guarantees that we'll get the info at the next frame, but since we can have multiple queries that + * are pending at the same time, we need to store some information to identify them when receiving the result callback + */ + + struct + { + uint64 bLastResult : 1; + uint64 LastProcessedFrameNumber : 63; + } FrameInfo; + + /** + * The 'FrameNumber' value can increase indefinitely while the 'Index' represents the number of queries that were + * already requested during this frame. So it shouldn't reach high values in the allocated 32 bits. + * Thanks to that we can reliable only use 31 bits for this value and thus have space to keep the bLastResult value + */ + struct + { + uint32 bLastResult : 1; + uint32 Index : 31; + uint32 FrameNumber; + } TraceInfo; + }; + + FAISightQueryVR(FPerceptionListenerID ListenerId = FPerceptionListenerID::InvalidID(), FAISightTargetVR::FTargetId Target = FAISightTargetVR::InvalidTargetId) + : ObserverId(ListenerId), TargetId(Target), Score(0), Importance(0), LastSeenLocation(FAISystem::InvalidLocation), UserData(0) + { + FrameInfo.bLastResult = false; + FrameInfo.LastProcessedFrameNumber = GFrameCounter; + } + + /** + * Note: This should only be called on queries that are queued up for later processing (in SightQueriesOutOfRange or SightQueriesOutOfRange) + */ + float GetAge() const + { + return (float)(GFrameCounter - FrameInfo.LastProcessedFrameNumber); + } + + /** + * Note: This should only be called on queries that are queued up for later processing (in SightQueriesOutOfRange or SightQueriesOutOfRange) + */ + void RecalcScore() + { + Score = GetAge() + Importance; + } + + void OnProcessed() + { + FrameInfo.LastProcessedFrameNumber = GFrameCounter; + } + + void ForgetPreviousResult() + { + LastSeenLocation = FAISystem::InvalidLocation; + SetLastResult(false); + } + + bool GetLastResult() const + { + return FrameInfo.bLastResult; + } + + void SetLastResult(const bool bValue) + { + FrameInfo.bLastResult = bValue; + } + + /** + * Note: This only be called for pending queries because it will erase the LastProcessedFrameNumber value + */ + void SetTraceInfo(const FTraceHandle& TraceHandle) + { + check((TraceHandle._Data.Index & (static_cast(1) << 31)) == 0); + TraceInfo.Index = TraceHandle._Data.Index; + TraceInfo.FrameNumber = TraceHandle._Data.FrameNumber; + } + + class FSortPredicate + { + public: + FSortPredicate() + {} + + bool operator()(const FAISightQueryVR& A, const FAISightQueryVR& B) const + { + return A.Score > B.Score; + } + }; +}; + + +struct FAISightQueryIDVR +{ + FPerceptionListenerID ObserverId; + FAISightTargetVR::FTargetId TargetId; + + FAISightQueryIDVR(FPerceptionListenerID ListenerId = FPerceptionListenerID::InvalidID(), FAISightTargetVR::FTargetId Target = FAISightTargetVR::InvalidTargetId) + : ObserverId(ListenerId), TargetId(Target) + { + } + + FAISightQueryIDVR(const FAISightQueryVR& Query) + : ObserverId(Query.ObserverId), TargetId(Query.TargetId) + { + } +}; + +DECLARE_DELEGATE_FiveParams(FOnPendingVisibilityQueryProcessedDelegateVR, const FAISightQueryID&, const bool, const float, const FVector&, const TOptional&); + + +UCLASS(ClassGroup = AI, config = Game) +class VREXPANSIONPLUGIN_API UAISense_Sight_VR : public UAISense +{ + GENERATED_UCLASS_BODY() + +public: + struct FDigestedSightProperties + { + float PeripheralVisionAngleCos; + float SightRadiusSq; + float AutoSuccessRangeSqFromLastSeenLocation; + float LoseSightRadiusSq; + float PointOfViewBackwardOffset; + float NearClippingRadiusSq; + uint8 AffiliationFlags; + + FDigestedSightProperties(); + FDigestedSightProperties(const UAISenseConfig_Sight_VR& SenseConfig); + }; + + typedef TMap FTargetsContainer; + FTargetsContainer ObservedTargets; + TMap DigestedProperties; + + /** The SightQueries are a n^2 problem and to reduce the sort time, they are now split between in range and out of range */ + /** Since the out of range queries only age as the distance component of the score is always 0, there is few need to sort them */ + /** In the majority of the cases most of the queries are out of range, so the sort time is greatly reduced as we only sort the in range queries */ + int32 NextOutOfRangeIndex = 0; + bool bSightQueriesOutOfRangeDirty = true; + TArray SightQueriesOutOfRange; + TArray SightQueriesInRange; + TArray SightQueriesPending; + +protected: + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + int32 MaxTracesPerTick; + + /** Maximum number of asynchronous traces that can be requested in a single update call*/ + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + int32 MaxAsyncTracesPerTick; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + int32 MinQueriesPerTimeSliceCheck; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + double MaxTimeSlicePerTick; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float HighImportanceQueryDistanceThreshold; + + float HighImportanceDistanceSquare; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float MaxQueryImportance; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float SightLimitQueryImportance; + + /** Defines the amount of async trace queries to prevent based on the number of pending queries at the start of an update. + * 1 means that the async trace budget is slashed by the pending queries count + * 0 means that the async trace budget is not impacted by the pending queries + */ + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float PendingQueriesBudgetReductionRatio; + + /** Defines if we are allowed to use asynchronous trace queries when there is no IAISightTargetInterface for a Target */ + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + bool bUseAsynchronousTraceForDefaultSightQueries; + + ECollisionChannel DefaultSightCollisionChannel; + + FOnPendingVisibilityQueryProcessedDelegateVR OnPendingCanBeSeenQueryProcessedDelegate; + FTraceDelegate OnPendingTraceQueryProcessedDelegate; + + UE_MT_DECLARE_RW_ACCESS_DETECTOR(QueriesListAccessDetector); + +public: + + virtual void PostInitProperties() override; + + void RegisterEvent(const FAISightEventVR& Event); + + virtual void RegisterSource(AActor& SourceActors) override; + virtual void UnregisterSource(AActor& SourceActor) override; + + virtual void OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget) override; + virtual void OnListenerForgetsAll(const FPerceptionListener& Listener) override; + +#if WITH_GAMEPLAY_DEBUGGER_MENU + virtual void DescribeSelfToGameplayDebugger(const UAIPerceptionSystem& PerceptionSystem, FGameplayDebuggerCategory& DebuggerCategory) const override; +#endif // WITH_GAMEPLAY_DEBUGGER_MENU + +protected: + virtual float Update() override; + + UAISense_Sight::EVisibilityResult ComputeVisibility(UWorld* World, FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const AActor* ListenerActor, FAISightTargetVR& Target, AActor* TargetActor, const FDigestedSightProperties& PropDigest, float& OutStimulusStrength, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested) const; + virtual bool ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQueryVR* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const; + UE_DEPRECATED(5.3, "Please use the UpdateQueryVisibilityStatus version which takes an Actor& instead.") + void UpdateQueryVisibilityStatus(FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor* TargetActor, const FVector& TargetLocation) const; + + void UpdateQueryVisibilityStatus(FAISightQueryVR& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor& TargetActor, const FVector& TargetLocation) const; + + + void OnPendingCanBeSeenQueryProcessed(const FAISightQueryID& QueryID, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData); + void OnPendingTraceQueryProcessed(const FTraceHandle& TraceHandle, FTraceDatum& TraceDatum); + void OnPendingQueryProcessed(const int32 SightQueryIndex, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData, const TOptional InTargetActor = NullOpt); + + + void OnNewListenerImpl(const FPerceptionListener& NewListener); + void OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener); + void OnListenerRemovedImpl(const FPerceptionListener& RemovedListener); + virtual void OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener) override; + + void GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction& OnAddedFunc = nullptr); + + + void RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction& OnRemoveFunc = nullptr); + void RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, const TFunction& OnRemoveFunc = nullptr); + + /** returns information whether new LoS queries have been added */ + bool RegisterTarget(AActor& TargetActor, const TFunction& OnAddedFunc = nullptr); + + float CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const; + bool RegisterNewQuery(const FPerceptionListener& Listener, const IGenericTeamAgentInterface* ListenersTeamAgent, const AActor& TargetActor, const FAISightTargetVR::FTargetId& TargetId, const FVector& TargetLocation, const FDigestedSightProperties& PropDigest, const TFunction& OnAddedFunc); + + + // Deprecated methods +public: + UE_DEPRECATED(4.25, "Not needed anymore done automatically at the beginning of each update.") + FORCEINLINE void SortQueries() {} + + enum FQueriesOperationPostProcess + { + DontSort, + Sort + }; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h new file mode 100644 index 0000000..2254c44 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h @@ -0,0 +1,106 @@ +#pragma once + +#include "PhysicsEngine/PhysicalAnimationComponent.h" +#include "CoreMinimal.h" +//#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "EngineDefines.h" +#if ENABLE_DRAW_DEBUG +#include "Chaos/DebugDrawQueue.h" +#endif +#include "VREPhysicalAnimationComponent.generated.h" + +struct FReferenceSkeleton; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FWeldedBoneDriverData +{ + GENERATED_BODY() +public: + FTransform RelativeTransform; + FName BoneName; + //FPhysicsShapeHandle ShapeHandle; + + FTransform LastLocal; + + FWeldedBoneDriverData() : + RelativeTransform(FTransform::Identity), + BoneName(NAME_None) + { + } + + /*FORCEINLINE bool operator==(const FPhysicsShapeHandle& Other) const + { + return (ShapeHandle == Other); + }*/ + + FORCEINLINE bool operator==(const FName& Other) const + { + return (BoneName == Other); + } +}; + +UCLASS(meta = (BlueprintSpawnableComponent), ClassGroup = Physics) +class VREXPANSIONPLUGIN_API UVREPhysicalAnimationComponent : public UPhysicalAnimationComponent +{ + GENERATED_UCLASS_BODY() + +public: + + /** Is the welded bone driver paused */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + bool bIsPaused; + + /** IF true then we will auto adjust the sleep settings of the body so that it won't rest during welded bone driving */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + bool bAutoSetPhysicsSleepSensitivity; + + /** The threshold multiplier to set on the body */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + float SleepThresholdMultiplier; + + /** The Base bone to use as the bone driver root */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + TArray BaseWeldedBoneDriverNames; + + UPROPERTY() + TArray BoneDriverMap; + + // Call to setup the welded body driver, initializes all mappings and caches shape contexts + // Requires that SetSkeletalMesh be called first + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void SetupWeldedBoneDriver(TArray BaseBoneNames); + + // Refreshes the welded bone driver, use this in cases where the body may have changed (such as welding to another body or switching physics) + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void RefreshWeldedBoneDriver(); + + // Sets the welded bone driver to be paused, you can also stop the component from ticking but that will also stop any physical animations going on + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void SetWeldedBoneDriverPaused(bool bPaused); + + UFUNCTION(BlueprintPure, Category = PhysicalAnimation) + bool IsWeldedBoneDriverPaused(); + + void SetupWeldedBoneDriver_Implementation(bool bReInit = false); + + //void OnWeldedMassUpdated(FBodyInstance* BodyInstance); + void UpdateWeldedBoneDriver(float DeltaTime); + + + /** If true then we will debug draw the mesh collision post shape alterations */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = WeldedBoneDriver) + bool bDebugDrawCollision = false; + +#if ENABLE_DRAW_DEBUG + void DebugDrawMesh(const TArray& DrawCommands); +#endif + + FTransform GetWorldSpaceRefBoneTransform(FReferenceSkeleton& RefSkel, int32 BoneIndex, int32 ParentBoneIndex); + FTransform GetRefPoseBoneRelativeTransform(USkeletalMeshComponent* SkeleMesh, FName BoneName, FName ParentBoneName); + + //FCalculateCustomPhysics OnCalculateCustomPhysics; + //void CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h new file mode 100644 index 0000000..6bf2ef1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h @@ -0,0 +1,191 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/PhysicsConstraintComponent.h" + +// Delete this eventually when the physics interface is fixed +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/PBDJointConstraintTypes.h" +#include "Chaos/PBDJointConstraintData.h" +#include "Chaos/Sphere.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" + +#include "VREPhysicsConstraintComponent.generated.h" + +/** +* A custom constraint component subclass that exposes additional missing functionality from the default one +*/ +UCLASS(ClassGroup = Physics, meta = (BlueprintSpawnableComponent), HideCategories = (Activation, "Components|Activation", Physics, Mobility), ShowCategories = ("Physics|Components|PhysicsConstraint", "VRE Constraint Settings")) +class VREXPANSIONPLUGIN_API UVREPhysicsConstraintComponent : public UPhysicsConstraintComponent +{ + + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void SetConstraintToForceBased(bool bUseForceConstraint) + { + + if (!ConstraintInstance.ConstraintHandle.IsValid()) + return; + + if (ConstraintInstance.ConstraintHandle->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast(ConstraintInstance.ConstraintHandle.Constraint)) + { + Constraint->SetLinearDriveForceMode(bUseForceConstraint ? Chaos::EJointForceMode::Force : Chaos::EJointForceMode::Acceleration); + Constraint->SetAngularDriveForceMode(bUseForceConstraint ? Chaos::EJointForceMode::Force : Chaos::EJointForceMode::Acceleration); + } + } + + //#endif + } + + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void GetConstraintReferenceFrame(EConstraintFrame::Type Frame, FTransform& RefFrame) + { + RefFrame = ConstraintInstance.GetRefFrame(Frame); + } + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + FTransform GetLocalPose(EConstraintFrame::Type ConstraintFrame) + { + if (ConstraintInstance.IsValidConstraintInstance()) + { + if (ConstraintFrame == EConstraintFrame::Frame1) + { + return FTransform(ConstraintInstance.PriAxis1, ConstraintInstance.SecAxis1, ConstraintInstance.PriAxis1 ^ ConstraintInstance.SecAxis1, ConstraintInstance.Pos1); + } + else + { + return FTransform(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, ConstraintInstance.Pos2); + } + + } + + return FTransform::Identity; + } + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void GetGlobalPose(EConstraintFrame::Type ConstraintFrame, FTransform& GlobalPose) + { + if (ConstraintInstance.IsValidConstraintInstance()) + { + GlobalPose = FPhysicsInterface::GetGlobalPose(ConstraintInstance.ConstraintHandle, ConstraintFrame); + } + else + GlobalPose = FTransform::Identity; + } + + // Gets the current linear distance in world space on the joint in +/- from the initial reference frame + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + FVector GetCurrentLinearDistance(EConstraintFrame::Type FrameOfReference) + { + EConstraintFrame::Type Frame2 = FrameOfReference; + EConstraintFrame::Type Frame1 = (FrameOfReference == EConstraintFrame::Frame1) ? EConstraintFrame::Frame2 : EConstraintFrame::Frame1; + + FTransform Frame1Trans = this->GetBodyTransform(Frame1); + FTransform Frame2Trans = this->GetBodyTransform(Frame2); + + FTransform LocalPose = GetLocalPose(Frame1); + FTransform LocalPose2 = GetLocalPose(Frame2); + + Frame1Trans.SetScale3D(FVector(1.f)); + Frame1Trans = LocalPose * Frame1Trans; + + FVector OffsetLoc = Frame1Trans.GetRotation().UnrotateVector(Frame1Trans.GetLocation() - Frame2Trans.GetLocation()); + FVector OffsetLoc2 = LocalPose2.GetRotation().UnrotateVector(LocalPose2.GetLocation()); + FVector FinalVec = OffsetLoc2 - OffsetLoc; + + return FinalVec; + } + + // Gets the angular offset on the constraint + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + FRotator GetAngularOffset() + { + return ConstraintInstance.AngularRotationOffset; + } + + // Sets the angular offset on the constraint and re-initializes it + UFUNCTION(BlueprintCallable, Category="VRE Physics Constraint Component") + void SetAngularOffset(FRotator NewAngularOffset) + { + + // If the constraint is broken then there is no reason to do everything below + // Just early out of it. + if (!ConstraintInstance.IsValidConstraintInstance() || ConstraintInstance.IsBroken()) + { + ConstraintInstance.AngularRotationOffset = NewAngularOffset; + return; + } + + // I could remove a full step if I calc delta in Frame2 local and then apply to the new + // Values. However I am keeping it like this for now, would require an extra inverse / relative calc, this may not even be slower + + FVector RefPos = ConstraintInstance.Pos2; + const float RefScale = FMath::Max(GetConstraintScale(), 0.01f); + if (GetBodyInstance(EConstraintFrame::Frame2)) + { + RefPos *= RefScale; + } + + FQuat AngRotOffset = ConstraintInstance.AngularRotationOffset.Quaternion(); + FQuat newAngRotOffset = NewAngularOffset.Quaternion(); + + FTransform A2Transform = GetBodyTransform(EConstraintFrame::Frame2); + A2Transform.RemoveScaling(); + + FTransform CurrentLocalFrame(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, ConstraintInstance.Pos2); + FTransform WorldLocalFrame = (CurrentLocalFrame * A2Transform); + + FVector WPri21 = GetComponentTransform().TransformVectorNoScale(AngRotOffset.GetForwardVector()); + FVector WOrth21 = GetComponentTransform().TransformVectorNoScale(AngRotOffset.GetRightVector()); + + FTransform OriginalRotOffset(WPri21, WOrth21, WPri21 ^ WOrth21, FVector::ZeroVector); + FQuat DeltaRot = WorldLocalFrame.GetRotation() * OriginalRotOffset.GetRotation().Inverse(); + DeltaRot.Normalize(); + + FVector WPri2 = GetComponentTransform().TransformVectorNoScale(newAngRotOffset.GetForwardVector()); + FVector WOrth2 = GetComponentTransform().TransformVectorNoScale(newAngRotOffset.GetRightVector()); + + WPri2 = DeltaRot.RotateVector(WPri2); + WOrth2 = DeltaRot.RotateVector(WOrth2); + + ConstraintInstance.PriAxis2 = A2Transform.InverseTransformVectorNoScale(WPri2); + ConstraintInstance.SecAxis2 = A2Transform.InverseTransformVectorNoScale(WOrth2); + ConstraintInstance.AngularRotationOffset = NewAngularOffset; + + FPhysicsInterface::ExecuteOnUnbrokenConstraintReadWrite(ConstraintInstance.ConstraintHandle, [&](const FPhysicsConstraintHandle& InUnbrokenConstraint) + { + FTransform URefTransform = FTransform(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, RefPos); + FPhysicsInterface::SetLocalPose(InUnbrokenConstraint, URefTransform, EConstraintFrame::Frame2); + }); + + return; + } + + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + void GetLinearLimits(float& LinearLimit) + { + LinearLimit = ConstraintInstance.GetLinearLimit(); + } + + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + void GetAngularLimits(float &Swing1Limit, float &Swing2Limit, float &TwistLimit) + { + Swing1Limit = ConstraintInstance.GetAngularSwing1Limit(); + Swing2Limit = ConstraintInstance.GetAngularSwing2Limit(); + TwistLimit = ConstraintInstance.GetAngularTwistLimit(); + } + + + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRE Constraint Settings") + //bool bSetAndMaintainCOMOnFrame2; + + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRE Constraint Settings") + // bool bUseForceConstraint; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h new file mode 100644 index 0000000..334198b --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h @@ -0,0 +1,451 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "GameFramework/Info.h" +#include "Components/WidgetComponent.h" +#include "ISpectatorScreenController.h" +#include "Templates/NonNullPointer.h" +//#include "CompositingElement.h" +#include "VRFullScreenUserWidget.generated.h" + +class FSceneViewport; +class FWidgetRenderer; +class FVRWidgetPostProcessHitTester; +class SConstraintCanvas; +class SVirtualWindow; +class SViewport; +class SWidget; +class ULevel; +class UMaterialInstanceDynamic; +class UMaterialInterface; +class UPostProcessComponent; +class UTextureRenderTarget2D; +class UWorld; + +#if WITH_EDITOR +class SLevelViewport; +#endif + + +UENUM(BlueprintType) +enum class EVRWidgetDisplayType : uint8 +{ + /** Do not display. */ + Inactive, + /** Display on a game viewport. */ + Viewport, + /** Display as a post process. */ + PostProcess, + /** Render to a texture and send to composure. */ + // Composure, +}; + + +USTRUCT() +struct FVRFullScreenUserWidget_Viewport +{ + GENERATED_BODY() + +public: + //FVRFullScreenUserWidget_Viewport(); + bool Display(UWorld* World, UUserWidget* Widget, TAttribute InDPIScale); + void Hide(UWorld* World); + //void Tick(UWorld* World, float DeltaSeconds); + +#if WITH_EDITOR + /** + * The viewport to use for displaying. + * Defaults to GetFirstActiveLevelViewport(). + + */ + TWeakPtr EditorTargetViewport; +#endif + +private: + + /** Constraint widget that contains the widget we want to display. */ + TWeakPtr FullScreenCanvasWidget; + +#if WITH_EDITOR + /** Level viewport the widget was added to. */ + TWeakPtr OverlayWidgetLevelViewport; +#endif +}; + +USTRUCT() +struct FVRFullScreenUserWidget_PostProcess +{ + GENERATED_BODY() + + FVRFullScreenUserWidget_PostProcess(); + void SetCustomPostProcessSettingsSource(TWeakObjectPtr InCustomPostProcessSettingsSource); + bool Display(UWorld* World, UUserWidget* Widget, bool bInRenderToTextureOnly, TAttribute InDPIScale); + void Hide(UWorld* World); + void Tick(UWorld* World, float DeltaSeconds); + + TSharedPtr VREXPANSIONPLUGIN_API GetSlateWindow() const; + +private: + //bool CreatePostProcessComponent(UWorld* World); + //void ReleasePostProcessComponent(); + + bool CreateRenderer(UWorld* World, UUserWidget* Widget, TAttribute InDPIScale); + void ReleaseRenderer(); + void TickRenderer(UWorld* World, float DeltaSeconds); + + FIntPoint CalculateWidgetDrawSize(UWorld* World); + bool IsTextureSizeValid(FIntPoint Size) const; + + void RegisterHitTesterWithViewport(UWorld* World); + void UnRegisterHitTesterWithViewport(); + + TSharedPtr GetViewport(UWorld* World) const; + float GetDPIScaleForPostProcessHitTester(TWeakObjectPtr World) const; + FPostProcessSettings* GetPostProcessSettings() const; + +public: + /** + * Post process material used to display the widget. + * SlateUI [Texture] + * TintColorAndOpacity [Vector] + * OpacityFromTexture [Scalar] + */ + //UPROPERTY(EditAnywhere, Category = PostProcess) + //UMaterialInterface* PostProcessMaterial; + + /** Tint color and opacity for this component. */ + //UPROPERTY(EditAnywhere, Category = PostProcess) + //FLinearColor PostProcessTintColorAndOpacity; + + /** Sets the amount of opacity from the widget's UI texture to use when rendering the translucent or masked UI to the viewport (0.0-1.0). */ + //UPROPERTY(EditAnywhere, Category = PostProcess, meta = (ClampMin = 0.0f, ClampMax = 1.0f)) + //float PostProcessOpacityFromTexture; + + /** The size of the rendered widget. */ + UPROPERTY(EditAnywhere, Category = PostProcess, meta=(InlineEditConditionToggle)) + bool bWidgetDrawSize; + + /** The size of the rendered widget. */ + UPROPERTY(EditAnywhere, Category = PostProcess, meta=(EditCondition= bWidgetDrawSize)) + FIntPoint WidgetDrawSize; + + /** Is the virtual window created to host the widget focusable? */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bWindowFocusable; + + /** The visibility of the virtual window created to host the widget. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + EWindowVisibility WindowVisibility; + + /** Register with the viewport for hardware input from the mouse and keyboard. It can and will steal focus from the viewport. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category= PostProcess) + bool bReceiveHardwareInput; + + /** The background color of the render target */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + FLinearColor RenderTargetBackgroundColor; + + /** The blend mode for the widget. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + EWidgetBlendMode RenderTargetBlendMode; + + /** List of composure layers that are expecting to use the WidgetRenderTarget. */ + //UPROPERTY(EditAnywhere, Category= PostProcess) + //TArray ComposureLayerTargets; + + /** The target to which the user widget is rendered. */ + UPROPERTY(Transient) + TObjectPtr WidgetRenderTarget; + + /** Only render to the UTextureRenderTarget2D - do not output to the final viewport. Unless DrawtoVRPreview is active */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bRenderToTextureOnly; + + /** If we should automatically try to draw and manage this to the VR Preview */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bDrawToVRPreview; + + // VR Spectator mode to use when active + UPROPERTY(EditAnywhere, Category = "User Interface") + ESpectatorScreenMode VRDisplayType; + + // VR Spectator mode to use when not active + UPROPERTY(EditAnywhere, Category = "User Interface") + ESpectatorScreenMode PostVRDisplayType; + +#if WITH_EDITOR + /** + * The viewport to use for displaying. + * + * Defaults to GetFirstActiveLevelViewport(). + */ + TWeakPtr EditorTargetViewport; +#endif +private: + /** Post process component used to add the material to the post process chain. */ + //UPROPERTY(Transient) + //UPostProcessComponent* PostProcessComponent; + + /** The dynamic instance of the material that the render target is attached to. */ + //UPROPERTY(Transient) + //UMaterialInstanceDynamic* PostProcessMaterialInstance; + + /** The slate window that contains the user widget content. */ + TSharedPtr SlateWindow; + + /** The slate viewport we are registered to. */ + TWeakPtr ViewportWidget; + + /** Helper class for drawing widgets to a render target. */ + FWidgetRenderer* WidgetRenderer; + + /** The size of the rendered widget */ + FIntPoint CurrentWidgetDrawSize; + + /** Hit tester when we want the hardware input. */ + TSharedPtr CustomHitTestPath; + +}; + +/** + * Will set the Widgets on a viewport either by Widgets are first rendered to a render target, then that render target is displayed in the world. + */ +UCLASS(BlueprintType, meta=(ShowOnlyInnerProperties)) +class VREXPANSIONPLUGIN_API UVRFullScreenUserWidget : public UObject +{ + GENERATED_BODY() + +public: + UVRFullScreenUserWidget(const FObjectInitializer& ObjectInitializer); + + //~ Begin UObject interface + virtual void BeginDestroy() override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + //~ End UObject Interface + + bool ShouldDisplay(UWorld* World) const; + EVRWidgetDisplayType GetDisplayType(UWorld* World) const; + bool IsDisplayed() const; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + bool IsDisplayRequested() + { + return bDisplayRequested; + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + virtual bool Display(UWorld* World); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + virtual void Hide(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + void SetIsHidden(bool bNewHidden) + { + if (bNewHidden) + { + Hide(); + } + else + { + if (World.IsValid()) + { + Display(World.Get()); + } + else + { + UWorld* myWorld = this->GetWorld(); + if (myWorld) + { + Display(myWorld); + } + } + } + } + + virtual void Tick(float DeltaTime); + + void SetDisplayTypes(EVRWidgetDisplayType InEditorDisplayType, EVRWidgetDisplayType InGameDisplayType, EVRWidgetDisplayType InPIEDisplayType); + void SetOverrideWidget(UUserWidget* InWidget); + + /** + * If using EVPWidgetDisplayType::PostProcess, you can specify a custom post process settings that should be modified. + * By default, a new post process component is added to AWorldSettings. + * + * @param InCustomPostProcessSettingsSource An object containing a FPostProcessSettings UPROPERTY() + */ + //void SetCustomPostProcessSettingsSource(TWeakObjectPtr InCustomPostProcessSettingsSource); + +#if WITH_EDITOR + /** + * Sets the TargetViewport to use on both the Viewport and the PostProcess class. + * + * Overrides the viewport to use for displaying. + * Defaults to GetFirstActiveLevelViewport(). + */ + void SetEditorTargetViewport(TWeakPtr InTargetViewport); + /** Resets the TargetViewport */ + void ResetEditorTargetViewport(); +#endif + +protected: + bool InitWidget(); + void ReleaseWidget(); + + FVector2D FindSceneViewportSize(); + float GetViewportDPIScale(); + +private: + void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld); + void OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources); + +public: + + /** The display type when the world is an editor world. */ + UPROPERTY(EditAnywhere, Category = "User Interface") + EVRWidgetDisplayType EditorDisplayType; + + /** The display type when the world is a game world. */ + UPROPERTY(EditAnywhere, Category = "User Interface") + EVRWidgetDisplayType GameDisplayType; + + /** The display type when the world is a PIE world. */ + UPROPERTY(EditAnywhere, Category = "User Interface", meta = (DisplayName = "PIE Display Type")) + EVRWidgetDisplayType PIEDisplayType; + + /** Behavior when the widget should be display by the slate attached to the viewport. */ + UPROPERTY(EditAnywhere, Category = "Viewport", meta = (ShowOnlyInnerProperties)) + FVRFullScreenUserWidget_Viewport ViewportDisplayType; + + /** The class of User Widget to create and display an instance of */ + UPROPERTY(EditAnywhere, Category = "User Interface") + TSubclassOf WidgetClass; + + /** Behavior when the widget should be display by a post process. */ + UPROPERTY(EditAnywhere, Category = "Post Process", meta = (ShowOnlyInnerProperties)) + FVRFullScreenUserWidget_PostProcess PostProcessDisplayType; + + // Get a pointer to the inner widget. + // Note: This should not be stored! + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + UUserWidget* GetWidget() const { return Widget; }; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + UTextureRenderTarget2D* GetPostProcessRenderTarget() const { return PostProcessDisplayType.WidgetRenderTarget; }; + +private: + /** The User Widget object displayed and managed by this component */ + UPROPERTY(Transient, DuplicateTransient) + TObjectPtr Widget; + + /** The world the widget is attached to. */ + TWeakObjectPtr World; + + /** How we currently displaying the widget. */ + EVRWidgetDisplayType CurrentDisplayType; + + /** The user requested the widget to be displayed. It's possible that some setting are invalid and the widget will not be displayed. */ + bool bDisplayRequested; + +#if WITH_EDITOR + /** + * The viewport to use for displaying. + * Defaults to GetFirstActiveLevelViewport(). + */ + TWeakPtr EditorTargetViewport; +#endif +}; + +/** + * Widgets are first rendered to a render target, then that render target is displayed in the world. + */ +UCLASS(Blueprintable, ClassGroup = "UserInterface", HideCategories = (Actor, Input, Movement, Collision, Rendering, "Utilities|Transformation", LOD), ShowCategories = ("Input|MouseInput", "Input|TouchInput")) +class VREXPANSIONPLUGIN_API AVRFullScreenUserWidgetActor : public AInfo +{ + GENERATED_BODY() + +public: + AVRFullScreenUserWidgetActor(const FObjectInitializer& ObjectInitializer); + + //~ Begin AActor interface + virtual void PostInitializeComponents() override; + virtual void PostLoad() override; + virtual void PostActorCreated() override; + virtual void Destroyed() override; + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaSeconds) override; + + /** If true the widget will be shown right away, if false you will need to set SetWidgetVisible(true) to show it */ + UPROPERTY(EditAnywhere, Category = "User Interface") + bool bShowOnInit; + + /** */ + UPROPERTY(VisibleAnywhere, Instanced, NoClear, Category = "User Interface", meta = (ShowOnlyInnerProperties)) + UVRFullScreenUserWidget* ScreenUserWidget; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UVRFullScreenUserWidget* GetPreviewWidgetComp(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetPIEDisplayType(EVRWidgetDisplayType NewDisplayType) + { + if (IsValid(ScreenUserWidget)) + { + ScreenUserWidget->PIEDisplayType = NewDisplayType; + ScreenUserWidget->Hide(); + RequestGameDisplay(); + } + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetGameDisplayType(EVRWidgetDisplayType NewDisplayType) + { + if (IsValid(ScreenUserWidget)) + { + ScreenUserWidget->GameDisplayType = NewDisplayType; + ScreenUserWidget->Hide(); + RequestGameDisplay(); + } + } + + // Set the widget to visible or not, this will be overriden by any changed to the actors hidden state + // IE: Setting actor to hidden will force this hidden as well, also setting the actor to visible will do the opposite + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetWidgetVisible(bool bIsVisible); + + virtual void SetActorHiddenInGame(bool bNewHidden) override + { + SetWidgetVisible(bNewHidden); + Super::SetActorHiddenInGame(bNewHidden); + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UUserWidget* GetWidget(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UTextureRenderTarget2D* GetPostProcessRenderTarget(); + +#if WITH_EDITOR + virtual bool ShouldTickIfViewportsOnly() const override { return true; } +#endif //WITH_EDITOR + //~ End AActor Interface + +private: + void RequestEditorDisplay(); + void RequestGameDisplay(); + +protected: + + +#if WITH_EDITORONLY_DATA + /** Display requested and will be executed on the first frame because we can't call BP function in the loading phase */ + bool bEditorDisplayRequested; +#endif +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h new file mode 100644 index 0000000..9b062fb --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h @@ -0,0 +1,189 @@ +// This class is intended to provide support for Local Mixed play between a mouse and keyboard player +// and a VR player. It is not needed outside of that use. + +#pragma once +#include "Engine/GameViewportClient.h" +//#include "Engine/Engine.h" +#include "CoreMinimal.h" + +#include "VRGameViewportClient.generated.h" + +UENUM(Blueprintable) +enum class EVRGameInputMethod : uint8 +{ + GameInput_Default, + GameInput_SharedKeyboardAndMouse, + GameInput_KeyboardAndMouseToPlayer2, +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVROnWindowCloseRequested); + + +/** +* Subclass this in a blueprint to overwrite how default input is passed around in engine between local characters. +* Generally used for passing keyboard / mouse input to a secondary local player for local mixed gameplay in VR +*/ +UCLASS(Blueprintable) +class VREXPANSIONPLUGIN_API UVRGameViewportClient : public UGameViewportClient +{ + GENERATED_UCLASS_BODY() + +public: + + // Event thrown when the window is closed + UPROPERTY(BlueprintAssignable, Category = "VRExpansionPlugin") + FVROnWindowCloseRequested BPOnWindowCloseRequested; + + // If true then forced window closing will be canceled (alt-f4, ect) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + bool bIgnoreWindowCloseCommands; + + // Input Method for the viewport + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + EVRGameInputMethod GameInputMethod; + + // If true we will also shuffle gamepad input according to the GameInputMethod + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + bool bAlsoChangeGamepPadInput; + + // A List of input categories to consider as valid gamepad ones if bIsGamepad is true on the input event + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + TArray GamepadInputCategories; + + bool IsValidGamePadKey(const FKey & InputKey) + { + if (!bAlsoChangeGamepPadInput) + return false; + + FName KeyCategory = InputKey.GetMenuCategory(); + + return GamepadInputCategories.Contains(KeyCategory); + } + + UFUNCTION() + bool EventWindowClosing() + { + if (BPOnWindowCloseRequested.IsBound()) + { + BPOnWindowCloseRequested.Broadcast(); + } + + if (bIgnoreWindowCloseCommands) + { + return false; + } + + return true; + } + + virtual void PostInitProperties() override + { + Super::PostInitProperties(); + + if (GamepadInputCategories.Num() < 1) + { + GamepadInputCategories.Add(FName(TEXT("Gamepad"))); + GamepadInputCategories.Add(FName(TEXT("PS4"))); + GamepadInputCategories.Add(FName(TEXT("XBox One"))); + GamepadInputCategories.Add(FName(TEXT("Touch"))); + GamepadInputCategories.Add(FName(TEXT("Gesture"))); + } + + OnWindowCloseRequested().BindUObject(this, &UVRGameViewportClient::EventWindowClosing); + } + + + virtual bool InputKey(const FInputKeyEventArgs& EventArgs) override + { + // Remap the old int32 ControllerId value to the new InputDeviceId + IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); + + // Early out if a gamepad event or ignoring input or is default setup / no GEngine + if(GameInputMethod == EVRGameInputMethod::GameInput_Default || IgnoreInput() || (EventArgs.IsGamepad() && !IsValidGamePadKey(EventArgs.Key))) + return Super::InputKey(EventArgs); + + const int32 NumLocalPlayers = World->GetGameInstance()->GetNumLocalPlayers(); + + // Also early out if number of players is less than 2 + if (NumLocalPlayers < 2) + return Super::InputKey(EventArgs); + + + // Its const so have to copy and send a new one in now that the function signature has changed + FInputKeyEventArgs NewStruct = EventArgs; + + if (GameInputMethod == EVRGameInputMethod::GameInput_KeyboardAndMouseToPlayer2) + { + // keyboard / mouse always go to player 0, so + 1 will be player 2 + NewStruct.ControllerId++; + + FPlatformUserId UserId = PLATFORMUSERID_NONE; + FInputDeviceId DeviceId = INPUTDEVICEID_NONE; + DeviceMapper.RemapControllerIdToPlatformUserAndDevice(NewStruct.ControllerId, UserId, NewStruct.InputDevice); + + return Super::InputKey(NewStruct); + } + else // Shared keyboard and mouse + { + bool bRetVal = false; + for (int32 i = 0; i < NumLocalPlayers; i++) + { + NewStruct.ControllerId = i; + + FPlatformUserId UserId = PLATFORMUSERID_NONE; + FInputDeviceId DeviceId = INPUTDEVICEID_NONE; + DeviceMapper.RemapControllerIdToPlatformUserAndDevice(NewStruct.ControllerId, UserId, NewStruct.InputDevice); + + bRetVal = Super::InputKey(NewStruct) || bRetVal; + } + + return bRetVal; + } + } + + virtual bool InputAxis(FViewport* tViewport, FInputDeviceId InputDevice, FKey Key, float Delta, float DeltaTime, int32 NumSamples = 1, bool bGamepad = false) override + { + // Remap the old int32 ControllerId value to the new InputDeviceId + IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); + + const int32 NumLocalPlayers = World->GetGameInstance()->GetNumLocalPlayers(); + + // Early out if a gamepad or not a mouse event (vr controller) or ignoring input or is default setup / no GEngine + if (((!Key.IsMouseButton() && !bGamepad) || (bGamepad && !IsValidGamePadKey(Key))) || NumLocalPlayers < 2 || GameInputMethod == EVRGameInputMethod::GameInput_Default || IgnoreInput()) + return Super::InputAxis(tViewport, InputDevice, Key, Delta, DeltaTime, NumSamples, bGamepad); + + if (GameInputMethod == EVRGameInputMethod::GameInput_KeyboardAndMouseToPlayer2) + { + // keyboard / mouse always go to player 0, so + 1 will be player 2 + int32 ControllerId = 1; + + FPlatformUserId UserId = PLATFORMUSERID_NONE; + FInputDeviceId DeviceId = INPUTDEVICEID_NONE; + DeviceMapper.RemapControllerIdToPlatformUserAndDevice(ControllerId, UserId, DeviceId); + + return Super::InputAxis(tViewport, DeviceId, Key, Delta, DeltaTime, NumSamples, bGamepad); + } + else // Shared keyboard and mouse + { + bool bRetVal = false; + for (int32 i = 0; i < NumLocalPlayers; i++) + { + FPlatformUserId UserId = PLATFORMUSERID_NONE; + FInputDeviceId DeviceId = INPUTDEVICEID_NONE; + DeviceMapper.RemapControllerIdToPlatformUserAndDevice(i, UserId, DeviceId); + + bRetVal = Super::InputAxis(tViewport, DeviceId, Key, Delta, DeltaTime, NumSamples, bGamepad) || bRetVal; + } + + return bRetVal; + } + + } +}; + +UVRGameViewportClient::UVRGameViewportClient(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + GameInputMethod = EVRGameInputMethod::GameInput_Default; + bAlsoChangeGamepPadInput = false; +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h new file mode 100644 index 0000000..b57100a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h @@ -0,0 +1,242 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Canvas.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/Console.h" +#include "Containers/UnrealString.h" +#include "Misc/OutputDeviceHelper.h" +#include "VRLogComponent.generated.h" + +/** +* +*/ +UENUM(BlueprintType) +enum class EBPVRConsoleDrawType : uint8 +{ + VRConsole_Draw_ConsoleOnly, + VRConsole_Draw_OutputLogOnly +// VRConsole_Draw_ConsoleAndOutputLog +}; + + +/** +* A single log message for the output log, holding a message and +* a style, for color and bolding of the message. +*/ +struct FVRLogMessage +{ + TSharedRef Message; + ELogVerbosity::Type Verbosity; + FName Category; + FName Style; + + FVRLogMessage(const TSharedRef& NewMessage, FName NewCategory, FName NewStyle = NAME_None) + : Message(NewMessage) + , Verbosity(ELogVerbosity::Log) + , Category(NewCategory) + , Style(NewStyle) + { + } + + FVRLogMessage(const TSharedRef& NewMessage, ELogVerbosity::Type NewVerbosity, FName NewCategory, FName NewStyle = NAME_None) + : Message(NewMessage) + , Verbosity(NewVerbosity) + , Category(NewCategory) + , Style(NewStyle) + { + } +}; + +// Custom Log output history class to hold the VR logs. +/** This class is to capture all log output even if the log window is closed */ +class FVROutputLogHistory : public FOutputDevice +{ +public: + + int32 MaxStoredMessages; + bool bIsDirty; + int32 MaxLineLength; + + FVROutputLogHistory() + { + MaxLineLength = 130; + bIsDirty = false; + MaxStoredMessages = 1000; + GLog->AddOutputDevice(this); + GLog->SerializeBacklog(this); + } + + ~FVROutputLogHistory() + { + // At shutdown, GLog may already be null + if (GLog != NULL) + { + GLog->RemoveOutputDevice(this); + } + } + + /** Gets all captured messages */ + const TArray< TSharedPtr >& GetMessages() const + { + return Messages; + } + +protected: + + virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override + { + // Capture all incoming messages and store them in history + CreateLogMessages(V, Verbosity, Category, Messages); + } + + bool CreateLogMessages(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category, TArray< TSharedPtr >& OutMessages) + { + if (Verbosity == ELogVerbosity::SetColor) + { + // Skip Color Events + return false; + } + else + { + FName Style; + if (Category == NAME_Cmd) + { + Style = FName(TEXT("Log.Command")); + } + else if (Verbosity == ELogVerbosity::Error) + { + Style = FName(TEXT("Log.Error")); + } + else if (Verbosity == ELogVerbosity::Warning) + { + Style = FName(TEXT("Log.Warning")); + } + else + { + Style = FName(TEXT("Log.Normal")); + } + + // Forget timestamps, I don't care about them and we have limited texture space to draw too + // Determine how to format timestamps + static ELogTimes::Type LogTimestampMode = ELogTimes::None; + /*if (UObjectInitialized() && !GExitPurge) + { + // Logging can happen very late during shutdown, even after the UObject system has been torn down, hence the init check above + LogTimestampMode = GetDefault()->LogTimestampMode; + }*/ + + const int32 OldNumMessages = OutMessages.Num(); + + // handle multiline strings by breaking them apart by line + TArray LineRanges; + FString CurrentLogDump = V; + FTextRange::CalculateLineRangesFromString(CurrentLogDump, LineRanges); + + bool bIsFirstLineInMessage = true; + for (const FTextRange& LineRange : LineRanges) + { + if (!LineRange.IsEmpty()) + { + FString Line = CurrentLogDump.Mid(LineRange.BeginIndex, LineRange.Len()); + Line = Line.ConvertTabsToSpaces(4); + + // Hard-wrap lines to avoid them being too long + /*static const */int32 HardWrapLen = MaxLineLength; + for (int32 CurrentStartIndex = 0; CurrentStartIndex < Line.Len();) + { + int32 HardWrapLineLen = 0; + if (bIsFirstLineInMessage) + { + FString MessagePrefix = FOutputDeviceHelper::FormatLogLine(Verbosity, Category, nullptr, LogTimestampMode); + + HardWrapLineLen = FMath::Min(HardWrapLen - MessagePrefix.Len(), Line.Len() - CurrentStartIndex); + FString HardWrapLine = Line.Mid(CurrentStartIndex, HardWrapLineLen); + + OutMessages.Add(MakeShareable(new FVRLogMessage(MakeShareable(new FString(MessagePrefix + HardWrapLine)), Verbosity, Category, Style))); + } + else + { + HardWrapLineLen = FMath::Min(HardWrapLen, Line.Len() - CurrentStartIndex); + FString HardWrapLine = Line.Mid(CurrentStartIndex, HardWrapLineLen); + + OutMessages.Add(MakeShareable(new FVRLogMessage(MakeShareable(new FString(MoveTemp(HardWrapLine))), Verbosity, Category, Style))); + } + + bIsFirstLineInMessage = false; + CurrentStartIndex += HardWrapLineLen; + } + } + } + + int numMessages = OutMessages.Num(); + if (numMessages > MaxStoredMessages) + { + OutMessages.RemoveAt(0, numMessages - MaxStoredMessages, true); + } + if (OldNumMessages != numMessages) + bIsDirty = true; + + return OldNumMessages != numMessages;//OutMessages.Num(); + } + } + +private: + + /** All log messages since this module has been started */ + TArray< TSharedPtr > Messages; +}; + +/** +* This class taps into the output log and console and renders them to textures so they can be viewed in levels. +* Generally used for debugging and testing in VR, also allows sending input to the console. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRLogComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UVRLogComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRLogComponent(); + + FVROutputLogHistory OutputLogHistory; + + virtual void PostInitProperties() override + { + Super::PostInitProperties(); + OutputLogHistory.MaxStoredMessages = FMath::Clamp(MaxStoredMessages, 100, 100000); + OutputLogHistory.MaxLineLength = FMath::Clamp(MaxLineLength, 50, 1000); + } + + UPROPERTY(BlueprintReadWrite,EditAnywhere, Category = "VRLogComponent|Console") + int32 MaxLineLength; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRLogComponent|Console") + int32 MaxStoredMessages; + + // Sets the console input text, can be used to clear the console or enter full or partial commands + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void SetConsoleText(FString Text); + + // Sends a key to the console - Console considers Released as final, flashes the cursor + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void SendKeyEventToConsole(FKey Key, EInputEvent KeyEvent); + + // Sends text to the console - Optionally returns at the end to "enter" the text, end flashes the cursor + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void AppendTextToConsole(FString Text, bool bReturnAtEnd = false); + + // Draw the console to a render target 2D + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true", DisplayName = "DrawConsoleToCanvasRenderTarget2D")) + bool DrawConsoleToRenderTarget2D(EBPVRConsoleDrawType DrawType, UTextureRenderTarget2D * Texture, float ScrollOffset, bool bForceDraw); + + + void DrawConsole(bool bLowerHalfOnly, UCanvas* Canvas); + void DrawOutputLog(bool bUpperHalfOnly, UCanvas* Canvas, float ScrollOffset); + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h new file mode 100644 index 0000000..a401f60 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerStart.h" +#include "Components/SceneComponent.h" +#include "VRPlayerStart.generated.h" + +/** +* A normal player start except I replaced the root component with a scene component so that the spawn +* transform will match our VR characters. +*/ +UCLASS(Blueprintable, ClassGroup = Common, hidecategories = Collision) +class VREXPANSIONPLUGIN_API AVRPlayerStart : public APlayerStart +{ + GENERATED_BODY() +private: + UPROPERTY() + TObjectPtr VRRootComp; +public: + + AVRPlayerStart(const FObjectInitializer& ObjectInitializer); + + /** Returns VRRootComp subobject **/ + class USceneComponent* GetVRRootComponent() const { return VRRootComp; } + + // Override this to use capsule even if it isn't the root component + virtual void GetSimpleCollisionCylinder(float& CollisionRadius, float& CollisionHalfHeight) const override; + virtual void FindBase() override; + virtual void Validate() override; +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h new file mode 100644 index 0000000..27ff397 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h @@ -0,0 +1,389 @@ +#pragma once +#include "TimerManager.h" +#include "VRRenderTargetManager.generated.h" + +class UVRRenderTargetManager; +class UCanvasRenderTarget2D; +class UCanvas; +class UTexture2D; +class UMaterial; +class APlayerController; + + +// #TODO: Dirty rects so don't have to send entire texture? + + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRReplicatedTextureStore +{ + GENERATED_BODY() +public: + + // Not automatically replicated, we are skipping it so that the array isn't checked + // We manually copy the data into the serialization buffer during netserialize and keep + // a flip flop dirty flag + + UPROPERTY(Transient) + TArray PackedData; + + UPROPERTY(Transient) + TArray UnpackedData; + + UPROPERTY(Transient) + uint32 Width; + + UPROPERTY() + uint32 Height; + + UPROPERTY(Transient) + bool bIsZipped; + + //UPROPERTY() + // bool bJPG; + //UPROPERTY(Transient) + EPixelFormat PixelFormat; + + FBPVRReplicatedTextureStore() + { + Width = 0; + Height = 0; + bIsZipped = false; + } + + void Reset() + { + PackedData.Reset(); + UnpackedData.Reset(); + Width = 0; + Height = 0; + PixelFormat = (EPixelFormat)0; + bIsZipped = false; + //bJPG = false; + } + + void PackData(); + void UnPackData(); + + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + +}; + +template<> +struct TStructOpsTypeTraits< FBPVRReplicatedTextureStore > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + + +USTRUCT() +struct FRenderDataStore { + GENERATED_BODY() + + TArray ColorData; + FRenderCommandFence RenderFence; + FIntPoint Size2D; + EPixelFormat PixelFormat; + + FRenderDataStore() { + } +}; + +UENUM(BlueprintType) +enum class ERenderManagerOperationType : uint8 +{ + Op_LineDraw = 0x00, + Op_TriDraw = 0x01, + Op_TexDraw = 0x02 +}; + + +USTRUCT() +struct FRenderManagerTri { + GENERATED_BODY() +public: + FVector2D P1; + FVector2D P2; + FVector2D P3; +}; + +USTRUCT() +struct FRenderManagerOperation { + GENERATED_BODY() +public: + + UPROPERTY() + uint32 OwnerID; + + UPROPERTY() + ERenderManagerOperationType OperationType; + + UPROPERTY() + FColor Color; + + UPROPERTY() + FVector2D P1; + + UPROPERTY() + FVector2D P2; + + UPROPERTY() + uint32 Thickness; + + UPROPERTY() + TArray Tris; + + UPROPERTY() + TSoftObjectPtr Texture; + + UPROPERTY() + TSoftObjectPtr Material; + + FRenderManagerOperation() + { + OwnerID = 0; + OperationType = ERenderManagerOperationType::Op_LineDraw; + Color = FColor::White; + P1 = FVector2D::ZeroVector; + P2 = FVector2D::ZeroVector; + Thickness = 0; + } + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); +}; +template<> +struct TStructOpsTypeTraits< FRenderManagerOperation > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +/** +* This class is used as a proxy to send owner only RPCs +*/ +UCLASS(ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API ARenderTargetReplicationProxy : public AActor +{ + GENERATED_BODY() + +public: + ARenderTargetReplicationProxy(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_Manager) + TObjectPtr OwningManager; + + UPROPERTY(Replicated) + uint32 OwnersID; + + UFUNCTION() + void OnRep_Manager(); + + UPROPERTY(Transient) + FBPVRReplicatedTextureStore TextureStore; + + UPROPERTY(Transient) + int32 BlobNum; + + bool bWaitingForManager; + + void SendInitMessage(); + + UFUNCTION() + void SendNextDataBlob(); + + FTimerHandle SendTimer_Handle; + FTimerHandle CheckManager_Handle; + + // Maximum size of texture blobs to use for sending (size of chunks that it gets broken down into) + UPROPERTY() + int32 TextureBlobSize; + + // Maximum bytes per second to send, you will want to play around with this and the + // MaxClientRate settings in config in order to balance the bandwidth and avoid saturation + // If you raise this above the max replication size of a 65k byte size then you will need + // To adjust the max size in engine network settings. + UPROPERTY() + int32 MaxBytesPerSecondRate; + + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override + { + if(SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + + Super::EndPlay(EndPlayReason); + } + + + UFUNCTION(Reliable, Server, WithValidation) + void SendLocalDrawOperations(const TArray& LocalRenderOperationStoreList); + + UFUNCTION(Reliable, Client) + void InitTextureSend(int32 Width, int32 Height, int32 TotalDataCount, int32 BlobCount, EPixelFormat PixelFormat, bool bIsZipped/*, bool bIsJPG*/); + + UFUNCTION(Reliable, Server, WithValidation) + void Ack_InitTextureSend(int32 TotalDataCount); + + UFUNCTION(Reliable, Client) + void ReceiveTextureBlob(const TArray& TextureBlob, int32 LocationInData, int32 BlobCount); + + UFUNCTION(Reliable, Server, WithValidation) + void Ack_ReceiveTextureBlob(int32 BlobCount); + + UFUNCTION(Reliable, Client) + void ReceiveTexture(const FBPVRReplicatedTextureStore&TextureData); + +}; + + + +USTRUCT() +struct FClientRepData { + GENERATED_BODY() + + UPROPERTY() + TObjectPtr PC; + + UPROPERTY() + TObjectPtr ReplicationProxy; + + UPROPERTY() + bool bIsRelevant; + + UPROPERTY() + bool bIsDirty; + + FClientRepData() + { + PC = nullptr; + ReplicationProxy = nullptr; + bIsRelevant = false; + bIsDirty = false; + } +}; + + +/** +* This class stores reading requests for rendertargets and iterates over them +* It returns the pixel data at the end of processing +* It references code from: https://github.com/TimmHess/UnrealImageCapture +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRRenderTargetManager : public UActorComponent +{ + GENERATED_BODY() + +public: + + UVRRenderTargetManager(const FObjectInitializer& ObjectInitializer); + + uint32 OwnerIDCounter; + + UPROPERTY(Transient) + TObjectPtr LocalProxy; + + TArray RenderOperationStore; + TArray LocalRenderOperationStore; + + // Rate to poll for drawing new operations + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + float DrawRate; + + UFUNCTION(Reliable, NetMultiCast, WithValidation) + void SendDrawOperations(const TArray& RenderOperationStoreList); + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddLineDrawOperation(FVector2D Point1, FVector2D Point2, FColor Color, int32 Thickness); + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddTextureDrawOperation(FVector2D Position, UTexture2D* TextureToDisplay); + + // Adds a draw operation for a triangle list, only takes the first vertex's color + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddMaterialTrianglesDrawOperation(TArray Tris, UMaterial* Material); + + void DrawOperation(UCanvas* Canvas, const FRenderManagerOperation& Operation); + + UFUNCTION() + void DrawPoll(); + + void DrawOperations(); + + UPROPERTY() + FTimerHandle DrawHandle; + + UPROPERTY(Transient) + bool bIsStoringImage; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + bool bInitiallyReplicateTexture; + + UPROPERTY(Transient) + bool bIsLoadingTextureBuffer; + + // Maximum size of texture blobs to use for sending (size of chunks that it gets broken down into) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 TextureBlobSize; + + // Maximum bytes per second to send, you will want to play around with this and the + // MaxClientRate settings in config in order to balance the bandwidth and avoid saturation + // If you raise this above the max replication size of a 65k byte size then you will need + // To adjust the max size in engine network settings. + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 MaxBytesPerSecondRate; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "RenderTargetManager") + TObjectPtr RenderTarget; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 RenderTargetWidth; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 RenderTargetHeight; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + FColor ClearColor; + + UPROPERTY(Transient) + TArray NetRelevancyLog; + + // Rate to poll for actor relevancy + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + float PollRelevancyTime; + + FTimerHandle NetRelevancyTimer_Handle; + + UPROPERTY(Transient) + FBPVRReplicatedTextureStore RenderTargetStore; + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|UtilityFunctions") + bool GenerateTrisFromBoxPlaneIntersection(UPrimitiveComponent* PrimToBoxCheck, FTransform WorldTransformOfPlane, const FPlane& LocalProjectionPlane, FVector2D PlaneSize, FColor UVColor, TArray& OutTris); + + // Create the render target that we are managing + void InitRenderTarget(); + + // Update the list of players that we are checking for relevancy + void UpdateRelevancyMap(); + + // Decompress the render target data to a texture and copy it to our managed render target + bool DeCompressRenderTarget2D(); + + // Queues storing the render target image to our buffer + void QueueImageStore(); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +protected: + TQueue RenderDataQueue; + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h new file mode 100644 index 0000000..83afc09 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h @@ -0,0 +1,158 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "GameFramework/Pawn.h" +#include "Engine/InputDelegateBinding.h" +#include "Components/InputComponent.h" +#include "GameFramework/PlayerController.h" +#include "VRVehiclePawn.generated.h" + + +/** +* This override of the base pawn allows for dual pawn usage in engine. +* It adds two new functions: SetBindToInput to bind input locally to the pawn and ForceSecondaryPossession which fakes possession so the +* player can control the pawn as if they were locally possessed into it in a multiplayer enviroment (no lag). +*/ +UCLASS(config = Game, BlueprintType) +class VREXPANSIONPLUGIN_API AVRVehiclePawn : public APawn +{ + GENERATED_BODY() + +public: + + /** Call this function to detach safely pawn from its controller, knowing that we will be destroyed soon. */ + /*UFUNCTION(BlueprintCallable, Category = "Pawn", meta = (Keywords = "Delete")) + virtual void DetachFromControllerPendingDestroy() override + { + if (Controller != NULL && Controller->GetPawn() == this) + { + Controller->PawnPendingDestroy(this); + if (Controller != NULL) + { + Controller->UnPossess(); + Controller = NULL; + } + } + }*/ + + + //UFUNCTION() + virtual void OnRep_Controller() override + { + if ((Controller != NULL) && (Controller->GetPawn() == NULL)) + { + // This ensures that APawn::OnRep_Pawn is called. Since we cant ensure replication order of APawn::Controller and AController::Pawn, + // if APawn::Controller is repped first, it will set AController::Pawn locally. When AController::Pawn is repped, the rep value will not + // be different from the just set local value, and OnRep_Pawn will not be called. This can cause problems if OnRep_Pawn does anything important. + // + // It would be better to never ever set replicated properties locally, but this is pretty core in the gameplay framework and I think there are + // lots of assumptions made in the code base that the Pawn and Controller will always be linked both ways. + //Controller->SetPawnFromRep(this); + + /*APlayerController* const PC = Cast(Controller); + if ((PC != NULL) && PC->bAutoManageActiveCameraTarget && (PC->PlayerCameraManager->ViewTarget.Target == Controller)) + { + PC->AutoManageActiveCameraTarget(this); + }*/ + } + + /*if (IsLocallyControlled()) + { + SetBindToInput(Controller, true); + }*/ + + } + + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetBindToInput(AController * CController, bool bBindToInput) + { + APlayerController * playe = Cast(CController); + + if (playe != NULL) + { + if(InputComponent) + playe->PopInputComponent(InputComponent); // Make sure it is off the stack + + if (!bBindToInput) + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return true; + } + else + { + // Set up player input component, if there isn't one already. + if (InputComponent == NULL) + { + InputComponent = CreatePlayerInputComponent(); + if (InputComponent) + { + SetupPlayerInputComponent(InputComponent); + InputComponent->RegisterComponent(); + + if (UInputDelegateBinding::SupportsInputDelegate(GetClass())) + { + InputComponent->bBlockInput = bBlockInput; + UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); + } + } + } + + if (InputComponent) + { + playe->PushInputComponent(InputComponent); // Enforce input as top of stack so it gets input first and can consume it + return true; + } + } + } + else + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return false; + } + + return false; + } + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool ForceSecondaryPossession(AController * NewController) + { + if (NewController) + { + PossessedBy(NewController); + } + else + { + UnPossessed(); + } + + return false; + //INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast(GetPawn()->GetMovementComponent()) : NULL; + //if (NetworkPredictionInterface) + //{ + // NetworkPredictionInterface->ResetPredictionData_Server(); + // } + + + // Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs. + // We're really just trying to avoid calling Restart() multiple times. + // if (!IsLocalPlayerController()) + // { + // GetPawn()->Restart(); + // } + // ClientRestart(GetPawn()); + + //ChangeState(NAME_Playing); + //if (bAutoManageActiveCameraTarget) + //{ + // AutoManageActiveCameraTarget(GetPawn()); + // ResetCameraMode(); + //} + //UpdateNavigationComponents(); + } + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h new file mode 100644 index 0000000..d29d88c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h @@ -0,0 +1,92 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "WheeledVehiclePawn.h" +#include "ChaosWheeledVehicleMovementComponent.h" + +#include "UObject/ObjectMacros.h" +#include "GameFramework/Pawn.h" +#include "Engine/InputDelegateBinding.h" +#include "Components/InputComponent.h" +#include "GameFramework/PlayerController.h" +#include "VRWheeledVehicle.generated.h" + + +/** +* This override of the base wheeled vehicle allows for dual pawn usage in engine. +*/ +UCLASS(config = Game, BlueprintType) +class VREXPANSIONPLUGIN_API AVRWheeledVehicle : public AWheeledVehiclePawn +//#endif +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetBindToInput(AController * CController, bool bBindToInput) + { + APlayerController * playe = Cast(CController); + + if (playe != NULL) + { + if(InputComponent) + playe->PopInputComponent(InputComponent); // Make sure it is off the stack + + if (!bBindToInput) + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return true; + } + else + { + // Set up player input component, if there isn't one already. + if (InputComponent == NULL) + { + InputComponent = CreatePlayerInputComponent(); + if (InputComponent) + { + SetupPlayerInputComponent(InputComponent); + InputComponent->RegisterComponent(); + + if (UInputDelegateBinding::SupportsInputDelegate(GetClass())) + { + InputComponent->bBlockInput = bBlockInput; + UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); + } + } + } + + if (InputComponent) + { + playe->PushInputComponent(InputComponent); // Enforce input as top of stack so it gets input first and can consume it + return true; + } + } + } + else + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return false; + } + + return false; + } + + // Calls the movement components override controller function + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetOverrideController(AController * NewController) + { + if (UChaosWheeledVehicleMovementComponent* MoveComp = Cast(this->GetMovementComponent())) + { + MoveComp->SetOverrideController(NewController); + return true; + } + + return false; + } + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h new file mode 100644 index 0000000..e1abb2f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h @@ -0,0 +1,348 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRExpansionFunctionLibrary.h" +#include "Components/ShapeComponent.h" +#include "VRTrackedParentInterface.h" +#include "ParentRelativeAttachmentComponent.generated.h" + +class AVRBaseCharacter; +class AVRCharacter; + +/** Delegate for notification when the PRC starts rotating in yaw to match the snap / yaw tolerances. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPRCBeginYawRotationEventSignature); + +// Type of rotation sampling to use +UENUM(BlueprintType) +enum class EVR_PRC_RotationMethod : uint8 +{ + // Rotate purely to the HMD yaw, default mode + PRC_ROT_HMD UMETA(DisplayName = "HMD rotation"), + + // Rotate to a blend between the HMD and Controller facing + PRC_ROT_HMDControllerBlend UMETA(DisplayName = "ROT HMD Controller Blend"), + + // Rotate to the controllers with behind the back detection, clamp within neck limit + PRC_ROT_ControllerHMDClamped UMETA(DisplayName = "Controller Clamped to HMD") +}; + + +/** +* A component that will track the HMD/Cameras location and YAW rotation to allow for chest/waist attachements. +* This is intended to be parented to the root component of a pawn, it will then either find and track the camera +* or use the HMD's position if one is connected. This allows it to work in multiplayer since the camera will +* have its position replicated. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UParentRelativeAttachmentComponent : public USceneComponent, public IVRTrackedParentInterface +{ + GENERATED_BODY() + +public: + UParentRelativeAttachmentComponent(const FObjectInitializer& ObjectInitializer); + virtual void InitializeComponent() override; + + // Rotation method to use for facing calulations + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + //EVR_PRC_RotationMethod YawRotationMethod; + + // Angle tolerance before we lerp / snap to the new rotation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary", meta = (ClampMin = "0", UIMin = "0")) + float YawTolerance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary", meta = (ClampMin = "0", UIMin = "0")) + float LerpSpeed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bLerpTransition; + + float LastRot; + float LastLerpVal; + float LerpTarget; + bool bWasSetOnce; + FTransform LeftControllerTrans; + FTransform RightControllerTrans; + + UPROPERTY(BlueprintAssignable, Category = "PRC Events") + FVRPRCBeginYawRotationEventSignature OnYawToleranceExceeded; + + // If true uses feet/bottom of the capsule as the base Z position for this component instead of the HMD/Camera Z position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUseFeetLocation = false; + + // If true uses center of capsule as the feet location instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary", meta = (EditCondition = "bUseFeetLocation")) + bool bUseCenterAsFeetLocation = false; // Should really be an enum with the above but that would make people change projects + + // An additional value added to the relative position of the PRC + // Can be used to offset the floor, or component heights if needed + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + FVector CustomOffset; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + //bool bOffsetByHMD; + + // If true, will not set rotation every frame, only position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bIgnoreRotationFromParent; + + // If true, this component will not perform logic in its tick, it will instead allow the character movement component to move it (unless the CMC is inactive, then it will go back to self managing) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUpdateInCharacterMovement; + +private: + UPROPERTY() + bool bIsPaused; +public: + + // Set the paused state of the PRC, if setting to paused then zero out rotation and zero out location will null out those values + UFUNCTION(BlueprintCallable, Category = "VRExpansionLibrary") + void SetPaused(bool bNewPaused, bool bZeroOutRotation, bool bZeroOutLocation); + + // If valid will use this as the tracked parent instead of the HMD / Parent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRTrackedParentInterface") + FBPVRWaistTracking_Info OptionalWaistTrackingParent; + + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) override + { + IVRTrackedParentInterface::Default_SetTrackedParent_Impl(NewParentComponent, WaistRadius, WaistTrackingMode, OptionalWaistTrackingParent, this); + } + + UPROPERTY() + TObjectPtr AttachChar; + UPROPERTY() + TObjectPtr AttachBaseChar; + + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void OnAttachmentChanged() override; + void UpdateTracking(float DeltaTime); + + bool IsLocallyControlled() const + { + // I like epics implementation better than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner->Role == ENetRole::ROLE_Authority); + } + + // Sets the rotation and location depending on the control variables. Trying to remove some code duplication here + inline void SetRelativeRotAndLoc(FVector NewRelativeLocation, FRotator NewRelativeRotation, float DeltaTime); + + FQuat GetCalculatedRotation(FRotator InverseRot, float DeltaTime) + { + FRotator FinalRot = FRotator::ZeroRotator; + + if (FPlatformMath::Abs(FRotator::ClampAxis(InverseRot.Yaw) - LastRot) < YawTolerance) // This is never true with the default value of 0.0f + { + if (!bWasSetOnce) + { + LastRot = FRotator::ClampAxis(InverseRot.Yaw); + LastLerpVal = LastRot; + LerpTarget = LastRot; + bWasSetOnce = true; + } + + if (bLerpTransition && !FMath::IsNearlyEqual(LastLerpVal, LerpTarget)) + { + LastLerpVal = FMath::FixedTurn(LastLerpVal, LerpTarget, LerpSpeed * DeltaTime); + FinalRot = FRotator(0, LastLerpVal, 0); + } + else + { + FinalRot = FRotator(0, LastRot, 0); + LastLerpVal = LastRot; + } + } + else + { + // If we are using a snap threshold + if (!FMath::IsNearlyZero(YawTolerance)) + { + LerpTarget = FRotator::ClampAxis(InverseRot.Yaw); + LastLerpVal = FMath::FixedTurn(/*LastRot*/LastLerpVal, LerpTarget, LerpSpeed * DeltaTime); + FinalRot = FRotator(0, LastLerpVal, 0); + OnYawToleranceExceeded.Broadcast(); + } + else // If we aren't then just directly set it to the correct rotation + { + FinalRot = FRotator(0, FRotator::ClampAxis(InverseRot.Yaw), 0); + } + + LastRot = FRotator::ClampAxis(InverseRot.Yaw); + } + + return FinalRot.Quaternion(); + } + + + + void RunSampling(FRotator &HMDRotation, FVector &HMDLocation) + { + /*switch(YawRotationMethod) + { + case EVR_PRC_RotationMethod::PRC_ROT_HMD: + { + return; + }break; + + case EVR_PRC_RotationMethod::PRC_ROT_HMDControllerBlend: + { + return; + }break; + + case EVR_PRC_RotationMethod::PRC_ROT_ControllerHMDClamped: + { + GetEstShoulderRotation(HMDRotation, HMDLocation); + return; + }break; + }*/ + + } + + // Get combined direction angle up + void GetEstShoulderRotation(FRotator &InputHMDRotation, FVector &InputHMDLocation) + { + /*float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f; + + // Position shoulder (neck) + FTransform shoulder = FTransform::Identity; + FVector headNeckDirectionVector = FVector(-.05f, 0.f, -1.f); + FVector neckShoulderDistance = FVector(-0.02f, 0.f, -.15f) * WorldToMeters; // World To Meters idealy + float headNeckDistance = 0.03f * WorldToMeters; // World To Meters idealy + + FVector headNeckOffset = InputHMDRotation.RotateVector(headNeckDirectionVector); + FVector targetPosition = InputHMDLocation + headNeckOffset * headNeckDistance; + shoulder.SetLocation(targetPosition + InputHMDRotation.RotateVector(neckShoulderDistance)); + + //DrawDebugSphere(GetWorld(), (shoulder * GetAttachParent()->GetComponentTransform()).GetLocation(), 4.0f, 32, FColor::White); + return; + */ + + /*if (IsLocallyControlled() && GEngine->XRSystem.IsValid()) + { + FVector Position = GetRelativeLocation(); + FRotator Orientation = GetRelativeRotation(); + + if (AttachBaseChar.IsValid()) + { + if (AttachBaseChar->LeftMotionController) + { + const bool bNewTrackedState = AttachBaseChar->LeftMotionController->GripPollControllerState(Position, Orientation, WorldToMeters); + bool bTracked = bNewTrackedState && CurrentTrackingStatus != ETrackingStatus::NotTracked; + + if (bTracked) + { + LeftControllerTrans = FTransform(Position, Orientation); + } + } + + if (AttachBaseChar->RightMotionController) + { + const bool bNewTrackedState = AttachBaseChar->RightMotionController->GripPollControllerState(Position, Orientation, WorldToMeters); + bool bTracked = bNewTrackedState && CurrentTrackingStatus != ETrackingStatus::NotTracked; + + if (bTracked) + { + RightControllerTrans = FTransform(Position, Orientation); + } + } + } + } + else if (AttachBaseChar.IsValid()) + { + LeftControllerTrans = AttachBaseChar->LeftMotionController->GetRelativeTransform(); + RightControllerTrans = AttachBaseChar->RightMotionController->GetRelativeTransform(); + } + + FVector LeftHand = LeftControllerTrans.GetLocation(); + FVector RightHand = RightControllerTrans.GetLocation(); + + //FRotator LeveledRel = CurrentTransforms.PureCameraYaw; + + FVector DistLeft = LeftHand - shoulder.Transform.GetLocation(); + FVector DistRight = RightHand - shoulder.Transform.GetLocation(); + + if (bIgnoreZPos) + { + DistLeft.Z = 0.0f; + DistRight.Z = 0.0f; + } + + FVector DirLeft = DistLeft.GetSafeNormal(); + FVector DirRight = DistRight.GetSafeNormal(); + + FVector CombinedDir = DirLeft + DirRight; + float FinalRot = FMath::RadiansToDegrees(FMath::Atan2(CombinedDir.Y, CombinedDir.X)); + + DetectHandsBehindHead(FinalRot, InputHMDRotation); + ClampHeadRotationDelta(FinalRot, InputHMDRotation); + + return FinalRot;*/ + } + + void DetectHandsBehindHead(float& TargetRot, FRotator HMDRotation) + { + /*float delta = FRotator::ClampAxis(FMath::FindDeltaAngleDegrees(TargetRot, LastTargetRot));// FRotator::ClampAxis(TargetRot - LastTargetRot); + + if (delta > 150.f && delta < 210.0f && !FMath::IsNearlyZero(LastTargetRot) && !bClampingHeadRotation) + { + bHandsBehindHead = !bHandsBehindHead; + if (bHandsBehindHead) + { + BehindHeadAngle = TargetRot; + } + else + { + BehindHeadAngle = 0.f; + } + } + else if (bHandsBehindHead) + { + float delta2 = FMath::Abs(FMath::FindDeltaAngleDegrees(TargetRot, BehindHeadAngle)); + + if (delta2 > 90.f) + { + bHandsBehindHead = !bHandsBehindHead; + BehindHeadAngle = 0.f; + } + } + + LastTargetRot = TargetRot; + + if (bHandsBehindHead) + { + TargetRot += 180.f; + }*/ + } + + // Clamp head rotation delta yaw + void ClampHeadRotationDelta(float& TargetRotation, FRotator HMDRotation) + { + /*float HeadRotation = FRotator::ClampAxis(CurrentTransforms.PureCameraYaw.Yaw); + float cTargetRotation = FRotator::ClampAxis(TargetRotation); + + float delta = HeadRotation - cTargetRotation; + + if ((delta > 80.f && delta < 180.f) || (delta < -180.f && delta >= -360.f + 80)) + { + TargetRotation = HeadRotation - 80.f; + bClampingHeadRotation = true; + } + else if ((delta < -80.f && delta > -180.f) || (delta > 180.f && delta < 360.f - 80.f)) + { + TargetRotation = HeadRotation + 80.f; + bClampingHeadRotation = true; + } + else + { + bClampingHeadRotation = false; + }*/ + } +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h new file mode 100644 index 0000000..3b060c8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h @@ -0,0 +1,168 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +//#include "Net/UnrealNetwork.h" +#include "Camera/CameraComponent.h" +#include "ReplicatedVRCameraComponent.generated.h" + +class AVRBaseCharacter; +class AVRCharacter; + +/** +* An overridden camera component that replicates its location in multiplayer +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UReplicatedVRCameraComponent : public UCameraComponent +{ + GENERATED_BODY() + +public: + UReplicatedVRCameraComponent(const FObjectInitializer& ObjectInitializer); + + + // If true, this component will not perform logic in its tick, it will instead allow the character movement component to move it (unless the CMC is inactive, then it will go back to self managing) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUpdateInCharacterMovement; + + UPROPERTY() + TObjectPtr AttachChar; + void UpdateTracking(float DeltaTime); + + virtual void OnAttachmentChanged() override; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Whether or not this component has authority within the frame*/ + bool bHasAuthority; + + /** Whether or not this component is currently on the network server*/ + bool bIsServer; + + FTransform LastRelativePosition; + bool bHadValidFirstVelocity; + + // If we should sample the velocity in world or local space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|ComponentVelocity") + bool bSampleVelocityInWorldSpace; + + // If true we will sample relative position for replication instead of the tracked settings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bFPSDebugMode = false; + + // For non view target positional updates + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bSetPositionDuringTick; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + //bool bOffsetByHMD; + + // If true will scale the tracking of the camera by TrackingScaler + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bScaleTracking; + + // A scale to be applied to the tracking of the camera + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bScaleTracking")) + FVector TrackingScaler; + + // If true we will use the minimum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitMinHeight; + + // The minimum height to allow for this camera + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.0", UIMin = "0.0", EditCondition = "bLimitMinHeight")) + float MinimumHeightAllowed; + + // If true will limit the max Z height that the camera is capable of reaching + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitMaxHeight; + + // If we are limiting the max height, this is the maximum allowed value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitMaxHeight")) + float MaxHeightAllowed; + + // If true will limit the maximum offset from center of the players tracked space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitBounds; + + // If we are limiting the maximum bounds, this is the maximum length of the vector from the center of the tracked space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitBounds")) + float MaximumTrackedBounds; + + /** Sets lock to hmd automatically based on if the camera is currently locally controlled or not */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + uint32 bAutoSetLockToHmd : 1; + + void ApplyTrackingParameters(FVector & OriginalPosition, bool bSkipLocZero = false); + bool HasTrackingParameters(); + + // Get Camera View is no longer required, they finally broke the HMD logic out into its own section!! + //virtual void GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView) override; + virtual void HandleXRCamera() override; + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedCameraTransform, Category = "ReplicatedCamera|Networking") + FBPVRComponentPosRep ReplicatedCameraTransform; + + FVector LastUpdatesRelativePosition = FVector::ZeroVector; + FRotator LastUpdatesRelativeRotation = FRotator::ZeroRotator; + + bool bLerpingPosition; + bool bReppedOnce; + + // Run the smoothing step + void RunNetworkedSmoothing(float DeltaTime); + + // Whether to smooth (lerp) between ticks for the replicated motion, DOES NOTHING if update rate is larger than FPS! + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "ReplicatedCamera|Networking") + bool bSmoothReplicatedMotion; + + // If true then we will use exponential smoothing with buffered correction + UPROPERTY(EditAnywhere, Category = "ReplicatedCamera|Networking|Smoothing", meta = (editcondition = "bSmoothReplicatedMotion")) + bool bUseExponentialSmoothing = true; + + // Timestep of smoothing translation + UPROPERTY(EditAnywhere, Category = "ReplicatedCamera|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float InterpolationSpeed = 25.0f; + + // Max distance to allow smoothing before snapping the remainder + UPROPERTY(EditAnywhere, Category = "ReplicatedCamera|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float NetworkMaxSmoothUpdateDistance = 50.f; + + // Max distance to allow smoothing before snapping entirely to the new position + UPROPERTY(EditAnywhere, Category = "ReplicatedCamera|Networking|Smoothing", meta = (editcondition = "bUseExponentialSmoothing")) + float NetworkNoSmoothUpdateDistance = 100.f; + + UFUNCTION() + virtual void OnRep_ReplicatedCameraTransform(); + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + // On dedicated servers the update rate should be at or lower than the server tick rate for smoothing to work + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "ReplicatedCamera|Networking") + float NetUpdateRate; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case. + float NetUpdateCount; + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendCameraTransform(FBPVRComponentPosRep NewTransform); + + // Pointer to an override to call from the owning character - this saves 7 bits a rep avoiding component IDs on the RPC + typedef void (AVRBaseCharacter::*VRBaseCharTransformRPC_Pointer)(FBPVRComponentPosRep NewTransform); + VRBaseCharTransformRPC_Pointer OverrideSendTransform; + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { + // I like epics new authority check more than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : false;// (MyOwner->Role == ENetRole::ROLE_Authority); + } + + //bool IsServer(); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h new file mode 100644 index 0000000..4405019 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "AIController.h" + +#include "VRAIController.generated.h" + + +UCLASS() +class VREXPANSIONPLUGIN_API AVRAIController : public AAIController +{ + GENERATED_BODY() + +public: + virtual FVector GetFocalPointOnActor(const AActor *Actor) const override; + + /** + * Checks line to center and top of other actor + * @param Other is the actor whose visibility is being checked. + * @param ViewPoint is eye position visibility is being checked from. If vect(0,0,0) passed in, uses current viewtarget's eye position. + * @param bAlternateChecks used only in AIController implementation + * @return true if controller's pawn can see Other actor. + */ + virtual bool LineOfSightTo(const AActor* Other, FVector ViewPoint = FVector(ForceInit), bool bAlternateChecks = false) const override; + //~ End AController Interface +}; + + +UCLASS() +class AVRDetourCrowdAIController : public AVRAIController +{ + GENERATED_BODY() +public: + AVRDetourCrowdAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h new file mode 100644 index 0000000..bebebce --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h @@ -0,0 +1,1955 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Engine/NetSerialization.h" +#include "PhysicsPublic.h" +//#include "EngineMinimal.h" +//#include "Components/PrimitiveComponent.h" + +#include "PhysicsEngine/ConstraintTypes.h" +#include "PhysicsEngine/ConstraintDrives.h" +#include "Physics/PhysicsInterfaceCore.h" +#include "VRBPDatatypes.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +// Custom movement modes for the characters +UENUM(BlueprintType) +enum class EVRCustomMovementMode : uint8 +{ + VRMOVE_Climbing UMETA(DisplayName = "Climbing"), + VRMOVE_LowGrav UMETA(DisplayName = "LowGrav"), + VRMOVE_Seated UMETA(DisplayName = "Seated"), + VRMOVE_SplineFollow UMETA(DisplayName = "SplineFollow") +// VRMove_Spider UMETA(DisplayName = "Spider") +}; + +// We use 6 bits for this so a maximum of 64 elements +UENUM(BlueprintType) +enum class EVRConjoinedMovementModes : uint8 +{ + C_MOVE_None = 0x00 UMETA(DisplayName = "None"), + C_MOVE_Walking = 0x01 UMETA(DisplayName = "Walking"), + C_MOVE_NavWalking = 0x02 UMETA(DisplayName = "Navmesh Walking"), + C_MOVE_Falling = 0x03 UMETA(DisplayName = "Falling"), + C_MOVE_Swimming = 0x04 UMETA(DisplayName = "Swimming"), + C_MOVE_Flying = 0x05 UMETA(DisplayName = "Flying"), + //C_MOVE_Custom = 0x06 UMETA(DisplayName = "Custom"), // Skip this, could technically get a Custom7 out of using this slot but who needs 7? + C_MOVE_MAX = 0x07 UMETA(Hidden), + C_VRMOVE_Climbing = 0x08 UMETA(DisplayName = "Climbing"), + C_VRMOVE_LowGrav = 0x09 UMETA(DisplayName = "LowGrav"), + //C_VRMOVE_Spider = 0x0A UMETA(DisplayName = "Spider"), + C_VRMOVE_Seated = 0x0A UMETA(DisplayName = "Seated"), + C_VRMOVE_SplineFollow = 0x0B UMETA(DisplayName = "SplineFollow"), // + // 0x0C + // 0x0D + // 0x0E + // 0x0F + // 0x10 + // 0x11 + // 0x12 + // 0x13 + // 0x14 + // 0x15 + // 0x16 + // 0x17 + // 0x18 + // 0x19 + C_VRMOVE_Custom1 = 0x1A UMETA(DisplayName = "Custom1"), + C_VRMOVE_Custom2 = 0x1B UMETA(DisplayName = "Custom2"), + C_VRMOVE_Custom3 = 0x1C UMETA(DisplayName = "Custom3"), + C_VRMOVE_Custom4 = 0x1D UMETA(DisplayName = "Custom4"), + C_VRMOVE_Custom5 = 0x1E UMETA(DisplayName = "Custom5"), + C_VRMOVE_Custom6 = 0x1F UMETA(DisplayName = "Custom6"), + C_VRMOVE_Custom7 = 0x20 UMETA(DisplayName = "Custom7"), + C_VRMOVE_Custom8 = 0x21 UMETA(DisplayName = "Custom8"), + C_VRMOVE_Custom9 = 0x22 UMETA(DisplayName = "Custom9"), + C_VRMOVE_Custom10 = 0x23 UMETA(DisplayName = "Custom10") +}; + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBPVRResultSwitch : uint8 +{ + // On Success + OnSucceeded, + // On Failure + OnFailed +}; + +// Which method of handling gripping conflict to take with client auth +UENUM(BlueprintType) +enum class EVRClientAuthConflictResolutionMode : uint8 +{ + // Do nothing + VRGRIP_CONFLICT_None, + // Give to the first to arrive + VRGRIP_CONFLICT_First, + // Give to the last to arrive + VRGRIP_CONFLICT_Last, + // Force all ends to drop their grip + VRGRIP_CONFLICT_DropAll +}; + +// Wasn't needed when final setup was realized +// Tracked device waist location +UENUM(Blueprintable) +enum class EBPVRWaistTrackingMode : uint8 +{ + // Waist is tracked from the front + VRWaist_Tracked_Front, + // Waist is tracked from the rear + VRWaist_Tracked_Rear, + // Waist is tracked from the left (self perspective) + VRWaist_Tracked_Left, + // Waist is tracked from the right (self perspective) + VRWaist_Tracked_Right +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRWaistTracking_Info +{ + GENERATED_BODY() +public: + + // Initial "Resting" location of the tracker parent, assumed to be the calibration zero + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FRotator RestingRotation; + + // Distance to offset to get center of waist from tracked parent location + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + float WaistRadius; + + // Controls forward vector + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + EBPVRWaistTrackingMode TrackingMode; + + // Tracked parent reference + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + TObjectPtr TrackedDevice; + + bool IsValid() + { + return TrackedDevice != nullptr; + } + + void Clear() + { + TrackedDevice = nullptr; + } + + FBPVRWaistTracking_Info(): + RestingRotation(FRotator::ZeroRotator), + WaistRadius(0.0f), + TrackingMode(EBPVRWaistTrackingMode::VRWaist_Tracked_Rear), + TrackedDevice(nullptr) + {} + +}; + + +/** Different methods for interpolating rotation between transforms */ +UENUM(BlueprintType) +enum class EVRLerpInterpolationMode : uint8 +{ + /** Shortest Path or Quaternion interpolation for the rotation. */ + QuatInterp, + + /** Rotor or Euler Angle interpolation. */ + EulerInterp, + + /** Dual quaternion interpolation, follows helix or screw-motion path between keyframes. */ + DualQuatInterp +}; + +template +class FBasicLowPassFilter +{ +public: + + /** Default constructor */ + FBasicLowPassFilter(filterType EmptyValueSet) + { + EmptyValue = EmptyValueSet; + Previous = EmptyValue; + PreviousRaw = EmptyValue; + bFirstTime = true; + } + + /** Calculate */ + filterType Filter(const filterType& InValue, const filterType& InAlpha) + { + + filterType Result = InValue; + if (!bFirstTime) + { + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = ((float*)&InAlpha)[i] * ((float*)&InValue)[i] + (1.0f - ((float*)&InAlpha)[i]) * ((float*)&Previous)[i]; + } + } + + bFirstTime = false; + Previous = Result; + PreviousRaw = InValue; + return Result; + } + + filterType EmptyValue; + + /** The previous filtered value */ + filterType Previous; + + /** The previous raw value */ + filterType PreviousRaw; + + /** If this is the first time doing a filter */ + bool bFirstTime; + +//private: + + const filterType CalculateCutoff(const filterType& InValue, float& MinCutoff, float& CutoffSlope) + { + filterType Result; + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = MinCutoff + CutoffSlope * FMath::Abs(((float*)&InValue)[i]); + } + return Result; + } + + const filterType CalculateAlpha(const filterType& InCutoff, const double InDeltaTime) + { + filterType Result; + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = CalculateAlphaTau(((float*)&InCutoff)[i], InDeltaTime); + } + return Result; + } + + inline const float CalculateAlphaTau(const float InCutoff, const double InDeltaTime) + { + const float tau = 1.0 / (2.0f * PI * InCutoff); + return 1.0f / (1.0f + tau / InDeltaTime); + } +}; + + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilter +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilter() : + MinCutoff(0.9f), + DeltaCutoff(1.0f), + CutoffSlope(0.007f), + RawFilter(FVector::ZeroVector), + DeltaFilter(FVector::ZeroVector) + {} + + FBPEuroLowPassFilter(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FVector::ZeroVector), + DeltaFilter(FVector::ZeroVector) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FVector RunFilterSmoothing(const FVector &InRawValue, const float &InDeltaTime); + +private: + + FBasicLowPassFilter RawFilter; + FBasicLowPassFilter DeltaFilter; + +}; + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +// This version is for Quaternions +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilterQuat +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilterQuat() : + MinCutoff(0.9f), + DeltaCutoff(1.0f), + CutoffSlope(0.007f), + RawFilter(FQuat::Identity), + DeltaFilter(FQuat::Identity) + {} + + FBPEuroLowPassFilterQuat(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FQuat::Identity), + DeltaFilter(FQuat::Identity) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FQuat RunFilterSmoothing(const FQuat& InRawValue, const float& InDeltaTime); + +private: + + FBasicLowPassFilter RawFilter; + FBasicLowPassFilter DeltaFilter; + +}; + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +// This version is for Transforms +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilterTrans +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilterTrans() : + MinCutoff(0.1f), + DeltaCutoff(10.0f), + CutoffSlope(10.0f), + RawFilter(FTransform::Identity), + DeltaFilter(FTransform::Identity) + {} + + FBPEuroLowPassFilterTrans(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FTransform::Identity), + DeltaFilter(FTransform::Identity) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FTransform RunFilterSmoothing(const FTransform& InRawValue, const float& InDeltaTime); + +private: + + FBasicLowPassFilter RawFilter; + FBasicLowPassFilter DeltaFilter; + +}; + +// The type of velocity tracking to perform on the motion controllers +UENUM(BlueprintType) +enum class EVRVelocityType : uint8 +{ + // Gets the frame by frame velocity + VRLOCITY_Default UMETA(DisplayName = "Default"), + + // Gets a running average velocity across a sample duration + VRLOCITY_RunningAverage UMETA(DisplayName = "Running Average"), + + // Gets the peak velocity across a sample duration + VRLOCITY_SamplePeak UMETA(DisplayName = "Sampled Peak") +}; + +// A structure used to store and calculate velocities in different ways +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPLowPassPeakFilter +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPLowPassPeakFilter() : + VelocitySamples(30), + VelocitySampleLogCounter(0) + {} + + // This is the number of samples to keep active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Samples") + int32 VelocitySamples; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Samples") + TArrayVelocitySampleLog; + + int32 VelocitySampleLogCounter; + + void Reset() + { + VelocitySampleLog.Reset(VelocitySamples); + } + + void AddSample(FVector NewSample) + { + if (VelocitySamples <= 0) + return; + + if (VelocitySampleLog.Num() != VelocitySamples) + { + VelocitySampleLog.Reset(VelocitySamples); + VelocitySampleLog.AddZeroed(VelocitySamples); + VelocitySampleLogCounter = 0; + } + + VelocitySampleLog[VelocitySampleLogCounter] = NewSample; + ++VelocitySampleLogCounter; + + if (VelocitySampleLogCounter >= VelocitySamples) + VelocitySampleLogCounter = 0; + } + + FVector GetPeak() const + { + FVector MaxValue = FVector::ZeroVector; + float ValueSizeSq = 0.f; + float CurSizeSq = 0.f; + + for (int i = 0; i < VelocitySampleLog.Num(); i++) + { + CurSizeSq = VelocitySampleLog[i].SizeSquared(); + if (CurSizeSq > ValueSizeSq) + { + MaxValue = VelocitySampleLog[i]; + ValueSizeSq = CurSizeSq; + } + } + + return MaxValue; + } +}; + +// Some static vars so we don't have to keep calculating these for our Smallest Three compression +namespace TransNetQuant +{ + static const float MinimumQ = -1.0f / 1.414214f; + static const float MaximumQ = +1.0f / 1.414214f; + static const float MinMaxQDiff = TransNetQuant::MaximumQ - TransNetQuant::MinimumQ; +} + +USTRUCT(/*noexport, */BlueprintType, Category = "VRExpansionLibrary|TransformNetQuantize", meta = (HasNativeMake = "/Script/VRExpansionPlugin.VRExpansionFunctionLibrary.MakeTransform_NetQuantize", HasNativeBreak = "/Script/VRExpansionPlugin.VRExpansionFunctionLibrary.BreakTransform_NetQuantize")) +struct FTransform_NetQuantize : public FTransform +{ + GENERATED_USTRUCT_BODY() + + FORCEINLINE FTransform_NetQuantize() : FTransform() + {} + + FORCEINLINE explicit FTransform_NetQuantize(ENoInit Init) : FTransform(Init) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FVector& InTranslation) : FTransform(InTranslation) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FQuat& InRotation) : FTransform(InRotation) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FRotator& InRotation) : FTransform(InRotation) + {} + + FORCEINLINE FTransform_NetQuantize(const FQuat& InRotation, const FVector& InTranslation, const FVector& InScale3D = FVector::OneVector) + : FTransform(InRotation, InTranslation, InScale3D) + {} + + FORCEINLINE FTransform_NetQuantize(const FRotator& InRotation, const FVector& InTranslation, const FVector& InScale3D = FVector::OneVector) + : FTransform(InRotation, InTranslation, InScale3D) + {} + + FORCEINLINE FTransform_NetQuantize(const FTransform& InTransform) : FTransform(InTransform) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FMatrix& InMatrix) : FTransform(InMatrix) + {} + + FORCEINLINE FTransform_NetQuantize(const FVector& InX, const FVector& InY, const FVector& InZ, const FVector& InTranslation) + : FTransform(InX, InY, InZ, InTranslation) + {} +public: + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + + // Serializes a quaternion with the Smallest Three alg + // Referencing the implementation from https://gafferongames.com/post/snapshot_compression/ + // Which appears to be the mostly widely referenced method + // Template variable is number of bits per element (IE: precision), lowest suggested is 9 + // While I wouldn't go to 16 as at that point it is 2 bits more expensive than FRotator::SerializeShort + // Due to the overhead 2 bits of sending out the largest index, a good default is likely 9-10 bits. + template + static bool SerializeQuat_SmallestThree(FArchive& Ar, FQuat &InQuat) + { + check(bits > 1 && bits <= 32); + + uint32 IntegerA = 0, IntegerB = 0, IntegerC = 0, LargestIndex = 0; + + // Get our scaler to not chop off the values + const float scale = float((1 << bits) - 1); + + if (Ar.IsSaving()) + { + InQuat.Normalize(); + const float abs_x = FMath::Abs(InQuat.X); + const float abs_y = FMath::Abs(InQuat.Y); + const float abs_z = FMath::Abs(InQuat.Z); + const float abs_w = FMath::Abs(InQuat.W); + + LargestIndex = 0; + float largest_value = abs_x; + + if (abs_y > largest_value) + { + LargestIndex = 1; + largest_value = abs_y; + } + + if (abs_z > largest_value) + { + LargestIndex = 2; + largest_value = abs_z; + } + + if (abs_w > largest_value) + { + LargestIndex = 3; + largest_value = abs_w; + } + + float a = 0.f; + float b = 0.f; + float c = 0.f; + + switch (LargestIndex) + { + case 0: + if (InQuat.X >= 0) + { + a = InQuat.Y; + b = InQuat.Z; + c = InQuat.W; + } + else + { + a = -InQuat.Y; + b = -InQuat.Z; + c = -InQuat.W; + } + break; + + case 1: + if (InQuat.Y >= 0) + { + a = InQuat.X; + b = InQuat.Z; + c = InQuat.W; + } + else + { + a = -InQuat.X; + b = -InQuat.Z; + c = -InQuat.W; + } + break; + + case 2: + if (InQuat.Z >= 0) + { + a = InQuat.X; + b = InQuat.Y; + c = InQuat.W; + } + else + { + a = -InQuat.X; + b = -InQuat.Y; + c = -InQuat.W; + } + break; + + case 3: + if (InQuat.W >= 0) + { + a = InQuat.X; + b = InQuat.Y; + c = InQuat.Z; + } + else + { + a = -InQuat.X; + b = -InQuat.Y; + c = -InQuat.Z; + } + break; + + default:break; + } + + const float normal_a = (a - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + const float normal_b = (b - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + const float normal_c = (c - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + + IntegerA = FMath::FloorToInt(normal_a * scale + 0.5f); + IntegerB = FMath::FloorToInt(normal_b * scale + 0.5f); + IntegerC = FMath::FloorToInt(normal_c * scale + 0.5f); + } + + // Serialize the bits + Ar.SerializeBits(&LargestIndex, 2); + Ar.SerializeBits(&IntegerA, bits); + Ar.SerializeBits(&IntegerB, bits); + Ar.SerializeBits(&IntegerC, bits); + + if (Ar.IsLoading()) + { + const float inverse_scale = 1.0f / scale; + + const float a = IntegerA * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + const float b = IntegerB * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + const float c = IntegerC * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + + switch (LargestIndex) + { + case 0: + { + InQuat.X = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.Y = a; + InQuat.Z = b; + InQuat.W = c; + } + break; + + case 1: + { + InQuat.X = a; + InQuat.Y = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.Z = b; + InQuat.W = c; + } + break; + + case 2: + { + InQuat.X = a; + InQuat.Y = b; + InQuat.Z = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.W = c; + } + break; + + case 3: + { + InQuat.X = a; + InQuat.Y = b; + InQuat.Z = c; + InQuat.W = FMath::Sqrt(1.f - a * a - b * b - c * c); + } + break; + + default: + { + InQuat.X = 0.f; + InQuat.Y = 0.f; + InQuat.Z = 0.f; + InQuat.W = 1.f; + } + } + + InQuat.Normalize(); + } + + return true; + } +}; + +template<> +struct TStructOpsTypeTraits< FTransform_NetQuantize > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +UENUM() +enum class EVRVectorQuantization : uint8 +{ + /** Each vector component will be rounded, preserving one decimal place. */ + RoundOneDecimal = 0, + /** Each vector component will be rounded, preserving two decimal places. */ + RoundTwoDecimals = 1 +}; + +UENUM() +enum class EVRRotationQuantization : uint8 +{ + /** Each rotation component will be rounded to 10 bits (1024 values). */ + RoundTo10Bits = 0, + /** Each rotation component will be rounded to a short. */ + RoundToShort = 1 +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FBPVRComponentPosRep +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(Transient) + FVector Position; + UPROPERTY(Transient) + FRotator Rotation; + + // The quantization level to use for the vector components + UPROPERTY(EditDefaultsOnly, Category = Replication, AdvancedDisplay) + EVRVectorQuantization QuantizationLevel; + + // The quantization level to use for the rotation components + // Using 10 bits mode saves approx 2.25 bytes per replication. + UPROPERTY(EditDefaultsOnly, Category = Replication, AdvancedDisplay) + EVRRotationQuantization RotationQuantizationLevel; + + FORCEINLINE uint16 CompressAxisTo10BitShort(float Angle) + { + // map [0->360) to [0->1024) and mask off any winding + return FMath::RoundToInt(Angle * 1024.f / 360.f) & 0xFFFF; + } + + + FORCEINLINE float DecompressAxisFrom10BitShort(uint16 Angle) + { + // map [0->1024) to [0->360) + return (Angle * 360.f / 1024.f); + } + + FBPVRComponentPosRep(): + QuantizationLevel(EVRVectorQuantization::RoundTwoDecimals), + RotationQuantizationLevel(EVRRotationQuantization::RoundToShort) + { + //QuantizationLevel = EVRVectorQuantization::RoundTwoDecimals; + Position = FVector::ZeroVector; + Rotation = FRotator::ZeroRotator; + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + // Defines the level of Quantization + //uint8 Flags = (uint8)QuantizationLevel; + Ar.SerializeBits(&QuantizationLevel, 1); // Only two values 0:1 + Ar.SerializeBits(&RotationQuantizationLevel, 1); // Only two values 0:1 + + // No longer using their built in rotation rep, as controllers will rarely if ever be at 0 rot on an axis and + // so the 1 bit overhead per axis is just that, overhead + //Rotation.SerializeCompressedShort(Ar); + + uint16 ShortPitch = 0; + uint16 ShortYaw = 0; + uint16 ShortRoll = 0; + + /** + * Valid range 100: 2^22 / 100 = +/- 41,943.04 (419.43 meters) + * Valid range 10: 2^18 / 10 = +/- 26,214.4 (262.144 meters) + * Pos rep is assumed to be in relative space for a tracked component, these numbers should be fine + */ + if (Ar.IsSaving()) + { + switch (QuantizationLevel) + { + case EVRVectorQuantization::RoundTwoDecimals: bOutSuccess &= SerializePackedVector<100, 22/*30*/>(Position, Ar); break; + case EVRVectorQuantization::RoundOneDecimal: bOutSuccess &= SerializePackedVector<10, 18/*24*/>(Position, Ar); break; + } + + switch (RotationQuantizationLevel) + { + case EVRRotationQuantization::RoundTo10Bits: + { + ShortPitch = CompressAxisTo10BitShort(Rotation.Pitch); + ShortYaw = CompressAxisTo10BitShort(Rotation.Yaw); + ShortRoll = CompressAxisTo10BitShort(Rotation.Roll); + + Ar.SerializeBits(&ShortPitch, 10); + Ar.SerializeBits(&ShortYaw, 10); + Ar.SerializeBits(&ShortRoll, 10); + }break; + + case EVRRotationQuantization::RoundToShort: + { + ShortPitch = FRotator::CompressAxisToShort(Rotation.Pitch); + ShortYaw = FRotator::CompressAxisToShort(Rotation.Yaw); + ShortRoll = FRotator::CompressAxisToShort(Rotation.Roll); + + Ar << ShortPitch; + Ar << ShortYaw; + Ar << ShortRoll; + }break; + } + } + else // If loading + { + //QuantizationLevel = (EVRVectorQuantization)Flags; + + switch (QuantizationLevel) + { + case EVRVectorQuantization::RoundTwoDecimals: bOutSuccess &= SerializePackedVector<100, 22/*30*/>(Position, Ar); break; + case EVRVectorQuantization::RoundOneDecimal: bOutSuccess &= SerializePackedVector<10, 18/*24*/>(Position, Ar); break; + } + + switch (RotationQuantizationLevel) + { + case EVRRotationQuantization::RoundTo10Bits: + { + Ar.SerializeBits(&ShortPitch, 10); + Ar.SerializeBits(&ShortYaw, 10); + Ar.SerializeBits(&ShortRoll, 10); + + Rotation.Pitch = DecompressAxisFrom10BitShort(ShortPitch); + Rotation.Yaw = DecompressAxisFrom10BitShort(ShortYaw); + Rotation.Roll = DecompressAxisFrom10BitShort(ShortRoll); + }break; + + case EVRRotationQuantization::RoundToShort: + { + Ar << ShortPitch; + Ar << ShortYaw; + Ar << ShortRoll; + + Rotation.Pitch = FRotator::DecompressAxisFromShort(ShortPitch); + Rotation.Yaw = FRotator::DecompressAxisFromShort(ShortYaw); + Rotation.Roll = FRotator::DecompressAxisFromShort(ShortRoll); + }break; + } + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FBPVRComponentPosRep > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +UENUM(Blueprintable) +enum class EGripCollisionType : uint8 +{ + /** Held items can be offset by geometry, uses physics for the offset, pushes physics simulating objects with weight taken into account. */ + InteractiveCollisionWithPhysics, + + // InteractiveCollisionWithVelocity, + + /** Held items can be offset by geometry, uses sweep for the offset, pushes physics simulating objects, no weight. */ + InteractiveCollisionWithSweep, + + /** Uses Stiffness and damping settings on collision, on no collision uses stiffness values 10x stronger so it has less play. */ + InteractiveHybridCollisionWithPhysics, + + /** Swaps back and forth between physics grip and a sweep type grip depending on if the held object will be colliding this frame or not. */ + InteractiveHybridCollisionWithSweep, + + /** Only sweeps movement, will not be offset by geomtry, still pushes physics simulating objects, no weight. */ + SweepWithPhysics, + + /** Does not sweep at all (does not trigger OnHitEvents), still pushes physics simulating objects, no weight. */ + PhysicsOnly, + + /** Free constraint to controller base, no rotational drives. */ + ManipulationGrip, + + /** Free constraint to controller base with a twist drive. */ + ManipulationGripWithWristTwist, + + /** Attachment grips use native attachment and only sets location / rotation if they differ, this grip always late updates*/ + AttachmentGrip, + + /** Custom grip is to be handled by the object itself, it just sends the TickGrip event every frame but doesn't move the object. */ + CustomGrip, + + /** A grip that does not tick or move, used for drop / grip events only and uses least amount of processing. */ + EventsOnly, + + /** Uses a hard constraint with no softness to lock them together, best used with ConstrainToPivot enabled and a bone chain. */ + LockedConstraint + +}; + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPHMDDeviceType : uint8 +{ + DT_OculusHMD,//Rift, + DT_PSVR, + //DT_Morpheus, + DT_ES2GenericStereoMesh, + DT_SteamVR, + DT_GearVR, + DT_GoogleVR, + DT_AppleARKit, + DT_GoogleARCore, + DT_Unknown +}; + +// Lerp states +UENUM(Blueprintable) +enum class EGripLerpState : uint8 +{ + StartLerp, + EndLerp, + //ConstantLerp_DEPRECATED, + NotLerping +}; + +// Secondary Grip Type +UENUM(Blueprintable) +enum class ESecondaryGripType : uint8 +{ + // No secondary grip + SG_None, + // Free secondary grip + SG_Free, + // Only secondary grip at a slot + SG_SlotOnly, + // Retain pos on drop + SG_Free_Retain, + // Retain pos on drop, slot only + SG_SlotOnly_Retain, + // Scaling with retain pos on drop + SG_FreeWithScaling_Retain, + // Scaling with retain pos on drop, slot only + SG_SlotOnlyWithScaling_Retain, + // Does nothing, just provides the events for personal use + SG_Custom, + // Does not track the hand, only scales the mesh with it + SG_ScalingOnly, +}; + +// Grip Late Update information +UENUM(Blueprintable) +enum class EGripLateUpdateSettings : uint8 +{ + LateUpdatesAlwaysOn, + LateUpdatesAlwaysOff, + NotWhenColliding, + NotWhenDoubleGripping, + NotWhenCollidingOrDoubleGripping +}; + +// Grip movement replication settings +// LocalOnly_Not_Replicated is useful for instant client grips +// that can be sent to the server and everyone locally grips it (IE: inventories that don't ever leave a player) +// Objects that need to be handled possibly by multiple players should be ran +// non locally gripped instead so that the server can validate grips instead. +// ClientSide_Authoritive will grip on the client instantly without server intervention and then send a notice to the server +// ClientSide_Authoritive_NoRep will grip on the client instantly without server intervention but will not rep the grip to the server +// that the grip was made +UENUM(Blueprintable) +enum class EGripMovementReplicationSettings : uint8 +{ + KeepOriginalMovement, + ForceServerSideMovement, + ForceClientSideMovement, + ClientSide_Authoritive, + ClientSide_Authoritive_NoRep +}; + +// Grip Target Type +UENUM(Blueprintable) +enum class EGripTargetType : uint8 +{ + ActorGrip, + ComponentGrip + //InteractibleActorGrip, + //InteractibleComponentGrip +}; + +// Lerp states +UENUM(Blueprintable) +enum class EGripInterfaceTeleportBehavior : uint8 +{ + /* Teleports entire actor */ + TeleportAllComponents, + /* Teleports by the location delta and not the calculated new position of the grip, useful for rag dolls*/ + DeltaTeleportation, + /* Only teleports an actor if the root component is held */ + OnlyTeleportRootComponent, + /* Just drop the grip on teleport */ + DropOnTeleport, + /* Teleporting is not allowed */ + DontTeleport +}; + +// Type of physics constraint to use +UENUM(Blueprintable) +enum class EPhysicsGripConstraintType : uint8 +{ + AccelerationConstraint = 0, + ForceConstraint = 1 +}; + +UENUM(Blueprintable) +enum class EPhysicsGripCOMType : uint8 +{ + /* Use the default setting for the specified grip type */ + COM_Default = 0, + /* Don't grip at center of mass (generally unstable as it grips at actor zero)*/ + COM_AtPivot = 1, + /* Set center of mass to grip location and grip there (default for interactible with physics) */ + COM_SetAndGripAt = 2, + /* Grip at center of mass but do not set it */ + COM_GripAt = 3, + /* Just grip at the controller location, but don't set COM (default for manipulation grips)*/ + COM_GripAtControllerLoc = 4 +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvGripPhysicsSettings +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings") + bool bUsePhysicsSettings; + + // Set the constraint force mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + EPhysicsGripConstraintType PhysicsConstraintType; + + // Set how the grips handle center of mass + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + EPhysicsGripCOMType PhysicsGripLocationSettings; + + // Turn off gravity during the grip, resolves the slight downward offset of the object with normal constraint strengths. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bTurnOffGravityDuringGrip; + + // Don't automatically (un)simulate the component/root on grip/drop, let the end user set it up instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bSkipSettingSimulating; + + // A multiplier to add to the stiffness of a grip that is then set as the MaxForce of the grip + // It is clamped between 0.00 and 512.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness, also Max force can be directly edited with SetAdvancedConstraintSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings"), meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "512.00", UIMax = "512.00")) + float LinearMaxForceCoefficient; + + // A multiplier to add to the stiffness of a grip that is then set as the MaxForce of the grip + // It is clamped between 0.00 and 512.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness, also Max force can be directly edited with SetAdvancedConstraintSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings"), meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "512.00", UIMax = "512.00")) + float AngularMaxForceCoefficient; + + // Use the custom angular values on this grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bUseCustomAngularValues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUseCustomAngularValues", ClampMin = "0.000", UIMin = "0.000")) + float AngularStiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUseCustomAngularValues", ClampMin = "0.000", UIMin = "0.000")) + float AngularDamping; + + FBPAdvGripPhysicsSettings(): + bUsePhysicsSettings(false), + PhysicsConstraintType(EPhysicsGripConstraintType::AccelerationConstraint), + PhysicsGripLocationSettings(EPhysicsGripCOMType::COM_Default), + bTurnOffGravityDuringGrip(false), + bSkipSettingSimulating(false), + LinearMaxForceCoefficient(0.f), + AngularMaxForceCoefficient(0.f), + bUseCustomAngularValues(false), + AngularStiffness(0.0f), + AngularDamping(0.0f)//, + //MaxForce(0.f) + {} + + FORCEINLINE bool operator==(const FBPAdvGripPhysicsSettings &Other) const + { + return (bUsePhysicsSettings == Other.bUsePhysicsSettings && + PhysicsGripLocationSettings == Other.PhysicsGripLocationSettings && + bTurnOffGravityDuringGrip == Other.bTurnOffGravityDuringGrip && + bSkipSettingSimulating == Other.bSkipSettingSimulating && + bUseCustomAngularValues == Other.bUseCustomAngularValues && + PhysicsConstraintType == Other.PhysicsConstraintType && + FMath::IsNearlyEqual(LinearMaxForceCoefficient, Other.LinearMaxForceCoefficient) && + FMath::IsNearlyEqual(AngularMaxForceCoefficient, Other.AngularMaxForceCoefficient) && + FMath::IsNearlyEqual(AngularStiffness, Other.AngularStiffness) && + FMath::IsNearlyEqual(AngularDamping, Other.AngularDamping) //&& + //FMath::IsNearlyEqual(MaxForce, Other.MaxForce) + ); + } + + FORCEINLINE bool operator!=(const FBPAdvGripPhysicsSettings &Other) const + { + return (bUsePhysicsSettings != Other.bUsePhysicsSettings || + PhysicsGripLocationSettings != Other.PhysicsGripLocationSettings || + bTurnOffGravityDuringGrip != Other.bTurnOffGravityDuringGrip || + bSkipSettingSimulating != Other.bSkipSettingSimulating || + bUseCustomAngularValues != Other.bUseCustomAngularValues || + PhysicsConstraintType != Other.PhysicsConstraintType || + !FMath::IsNearlyEqual(LinearMaxForceCoefficient, Other.LinearMaxForceCoefficient) || + !FMath::IsNearlyEqual(AngularMaxForceCoefficient, Other.AngularMaxForceCoefficient) || + !FMath::IsNearlyEqual(AngularStiffness, Other.AngularStiffness) || + !FMath::IsNearlyEqual(AngularDamping, Other.AngularDamping) //|| + //!FMath::IsNearlyEqual(MaxForce, Other.MaxForce) + ); + } + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + //Ar << bUsePhysicsSettings; + Ar.SerializeBits(&bUsePhysicsSettings, 1); + + if (bUsePhysicsSettings) + { + //Ar << bDoNotSetCOMToGripLocation; + Ar.SerializeBits(&PhysicsGripLocationSettings, 3); // This only has four elements + + //Ar << PhysicsConstraintType; + Ar.SerializeBits(&PhysicsConstraintType, 1); // This only has two elements + + //Ar << bTurnOffGravityDuringGrip; + Ar.SerializeBits(&bTurnOffGravityDuringGrip, 1); + Ar.SerializeBits(&bSkipSettingSimulating, 1); + + + // This is 0.0 - 512.0, using compression to get it smaller, 8 bits = max 256 + 1 bit for sign and 7 bits precision for 128 / full 2 digit precision + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<512, 17>(LinearMaxForceCoefficient, Ar); + bOutSuccess &= WriteFixedCompressedFloat<512, 17>(AngularMaxForceCoefficient, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<512, 17>(LinearMaxForceCoefficient, Ar); + bOutSuccess &= ReadFixedCompressedFloat<512, 17>(AngularMaxForceCoefficient, Ar); + } + + + + //Ar << bUseCustomAngularValues; + Ar.SerializeBits(&bUseCustomAngularValues, 1); + + if (bUseCustomAngularValues) + { + Ar << AngularStiffness; + Ar << AngularDamping; + } + + //Ar << MaxForce; + } + + bOutSuccess = true; + return bOutSuccess; + } +}; + +template<> +struct TStructOpsTypeTraits< FBPAdvGripPhysicsSettings > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvGripSettings +{ + GENERATED_BODY() +public: + + // Priority of this item when being gripped, (Higher is more priority) + // This lets you prioritize whether an object should be gripped over another one when both + // collide with traces or overlaps. #Note: Currently not implemented in the plugin, here for your use. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + uint8 GripPriority; + + // If true, will set the owner of actor grips on grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + bool bSetOwnerOnGrip; + + // If true, we will be bypassed on global lerp operations + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + bool bDisallowLerping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + FBPAdvGripPhysicsSettings PhysicsSettings; + + FBPAdvGripSettings() : + GripPriority(1), + bSetOwnerOnGrip(1), + bDisallowLerping(0) + {} + + FBPAdvGripSettings(int GripPrio) : + GripPriority(GripPrio), + bSetOwnerOnGrip(1), + bDisallowLerping(0) + {} +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPSecondaryGripInfo +{ + GENERATED_BODY() +public: + + // For multi grip situations + UPROPERTY(BlueprintReadOnly, Category = "SecondaryGripInfo") + bool bHasSecondaryAttachment; + + UPROPERTY(BlueprintReadOnly, Category = "SecondaryGripInfo") + TObjectPtr SecondaryAttachment; + + UPROPERTY(BlueprintReadWrite, Category = "SecondaryGripInfo") + FTransform_NetQuantize SecondaryRelativeTransform; + + UPROPERTY(BlueprintReadWrite, Category = "SecondaryGripInfo") + bool bIsSlotGrip; + + UPROPERTY(BlueprintReadWrite, Category = "SecondaryGripInfo") + FName SecondarySlotName; + + // Lerp transitions + // Max value is 16 seconds with two decimal precision, this is to reduce replication overhead + UPROPERTY() + float LerpToRate; + + // Filled in from the tick code so users can activate and deactivate grips based on this + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "SecondaryGripInfo") + float SecondaryGripDistance; + + // These are not replicated, they don't need to be + EGripLerpState GripLerpState; + float curLerp; + + // Store values for frame by frame changes of secondary grips + FVector LastRelativeLocation; + + void ClearNonReppingItems() + { + SecondaryGripDistance = 0.0f; + GripLerpState = EGripLerpState::NotLerping; + curLerp = 0.0f; + } + + FBPSecondaryGripInfo(): + bHasSecondaryAttachment(false), + SecondaryAttachment(nullptr), + SecondaryRelativeTransform(FTransform::Identity), + bIsSlotGrip(false), + SecondarySlotName(NAME_None), + LerpToRate(0.0f), + SecondaryGripDistance(0.0f), + GripLerpState(EGripLerpState::NotLerping), + curLerp(0.0f), + LastRelativeLocation(FVector::ZeroVector) + {} + + // Adding this override to handle the fact that repped versions don't send relative loc and slot grip + // We don't want to override relative loc with 0,0,0 when it is in end lerp as otherwise it lerps wrong + FORCEINLINE FBPSecondaryGripInfo& RepCopy(const FBPSecondaryGripInfo& Other) + { + this->bHasSecondaryAttachment = Other.bHasSecondaryAttachment; + this->SecondaryAttachment = Other.SecondaryAttachment; + + if (bHasSecondaryAttachment) + { + this->SecondaryRelativeTransform = Other.SecondaryRelativeTransform; + this->bIsSlotGrip = Other.bIsSlotGrip; + this->SecondarySlotName = Other.SecondarySlotName; + } + + this->LerpToRate = Other.LerpToRate; + return *this; + } + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + //Ar << bHasSecondaryAttachment; + Ar.SerializeBits(&bHasSecondaryAttachment, 1); + + if (bHasSecondaryAttachment) + { + Ar << SecondaryAttachment; + //Ar << SecondaryRelativeLocation; + SecondaryRelativeTransform.NetSerialize(Ar, Map, bOutSuccess); + + //Ar << bIsSlotGrip; + Ar.SerializeBits(&bIsSlotGrip, 1); + + Ar << SecondarySlotName; + } + + // This is 0.0 - 16.0, using compression to get it smaller, 4 bits = max 16 + 1 bit for sign and 7 bits precision for 128 / full 2 digit precision + if (Ar.IsSaving()) + bOutSuccess &= WriteFixedCompressedFloat<16, 12>(LerpToRate, Ar); + else + bOutSuccess &= ReadFixedCompressedFloat<16, 12>(LerpToRate, Ar); + + //Ar << LerpToRate; + return true; + } +}; + +template<> +struct TStructOpsTypeTraits< FBPSecondaryGripInfo > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +#define INVALID_VRGRIP_ID 0 + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPActorGripInformation +{ + GENERATED_BODY() +public: + + // Hashed unique ID to identify this grip instance + UPROPERTY(BlueprintReadOnly, Category = "Settings") + uint8 GripID; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripTargetType GripTargetType; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + TObjectPtr GrippedObject; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripCollisionType GripCollisionType; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + EGripLateUpdateSettings GripLateUpdateSetting; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + bool bColliding; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FTransform_NetQuantize RelativeTransform; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + bool bIsSlotGrip; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FName GrippedBoneName; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FName SlotName; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripMovementReplicationSettings GripMovementReplicationSetting; + + // Whether the grip is currently paused + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + bool bIsPaused; + + // Only true in one specific circumstance, when you are a simulated client + // and the grip has been dropped but replication on the array hasn't deleted + // the entry yet. We cannot remove the entry as it can corrupt the array. + // this lets end users check against the grip to ignore it. + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + bool bIsPendingKill; + + // When true, will lock a hybrid grip into its collision state + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + bool bLockHybridGrip; + + // I would have loved to have both of these not be replicated (and in normal grips they wouldn't have to be) + // However for serialization purposes and Client_Authority grips they need to be.... + UPROPERTY() + bool bOriginalReplicatesMovement; + UPROPERTY() + bool bOriginalGravity; + + UPROPERTY(BlueprintReadOnly, Category = "Settings") + float Damping; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + float Stiffness; + + UPROPERTY(BlueprintReadOnly, Category = "Settings") + FBPAdvGripSettings AdvancedGripSettings; + + // For multi grip situations + UPROPERTY(BlueprintReadOnly, Category = "Settings") + FBPSecondaryGripInfo SecondaryGripInfo; + + // Optional Additive Transform for programmatic animation + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + FTransform AdditionTransform; + + // Distance from the target point for the grip + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + float GripDistance; + + // Locked transitions for swept movement so they don't just rotate in place on contact + bool bIsLocked; + FQuat LastLockedRotation; + + // For delta teleport and any future calculations we want to do + FTransform LastWorldTransform; + bool bSetLastWorldTransform; + + // Need to skip one frame of length check post teleport with constrained objects, the constraint may have not been updated yet. + bool bSkipNextTeleportCheck; + + // Need to skip one frame of length check post teleport with constrained objects, the constraint may have not been updated yet. + bool bSkipNextConstraintLengthCheck; + + // Lerp settings if we are using global lerping + float CurrentLerpTime; + float LerpSpeed; + FTransform OnGripTransform; + + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + bool bIsLerping; + + bool IsLocalAuthGrip() + { + return GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep; + } + + // If the grip is valid + bool IsValid() const + { + return (!bIsPendingKill && GripID != INVALID_VRGRIP_ID && GrippedObject && IsValidChecked(GrippedObject)); + } + + // Both valid and is not paused + bool IsActive() const + { + return (!bIsPendingKill && GripID != INVALID_VRGRIP_ID && GrippedObject && IsValidChecked(GrippedObject) && !bIsPaused); + } + + // Cached values - since not using a full serialize now the old array state may not contain what i need to diff + // I set these in On_Rep now and check against them when new replications happen to control some actions. + struct FGripValueCache + { + bool bWasInitiallyRepped; + uint8 CachedGripID; + + FGripValueCache() : + bWasInitiallyRepped(false), + CachedGripID(INVALID_VRGRIP_ID) + {} + + }ValueCache; + + void ClearNonReppingItems() + { + ValueCache = FGripValueCache(); + bColliding = false; + bIsLocked = false; + LastLockedRotation = FQuat::Identity; + LastWorldTransform.SetIdentity(); + bSetLastWorldTransform = false; + bSkipNextTeleportCheck = false; + bSkipNextConstraintLengthCheck = false; + bIsPaused = false; + bIsPendingKill = false; + bLockHybridGrip = false; + AdditionTransform = FTransform::Identity; + GripDistance = 0.0f; + CurrentLerpTime = 0.f; + LerpSpeed = 0.f; + OnGripTransform = FTransform::Identity; + bIsLerping = false; + + // Clear out the secondary grip + SecondaryGripInfo.ClearNonReppingItems(); + } + + // Adding this override to keep un-repped variables from repping over from Client Auth grips + FORCEINLINE FBPActorGripInformation& RepCopy(const FBPActorGripInformation& Other) + { + this->GripID = Other.GripID; + this->GripTargetType = Other.GripTargetType; + this->GrippedObject = Other.GrippedObject; + this->GripCollisionType = Other.GripCollisionType; + this->GripLateUpdateSetting = Other.GripLateUpdateSetting; + this->RelativeTransform = Other.RelativeTransform; + this->bIsSlotGrip = Other.bIsSlotGrip; + this->GrippedBoneName = Other.GrippedBoneName; + this->SlotName = Other.SlotName; + this->GripMovementReplicationSetting = Other.GripMovementReplicationSetting; + //this->bOriginalReplicatesMovement = Other.bOriginalReplicatesMovement; + //this->bOriginalGravity = Other.bOriginalGravity; + this->Damping = Other.Damping; + this->Stiffness = Other.Stiffness; + this->AdvancedGripSettings = Other.AdvancedGripSettings; + this->SecondaryGripInfo.RepCopy(Other.SecondaryGripInfo); // Run the replication copy version so we don't overwrite vars + //this->SecondaryGripInfo = Other.SecondaryGripInfo; + + return *this; + } + + + FORCEINLINE AActor * GetGrippedActor() const + { + return Cast(GrippedObject); + } + + FORCEINLINE UPrimitiveComponent * GetGrippedComponent() const + { + return Cast(GrippedObject); + } + + //Check if a grip is the same as another, the only things I check for are the actor / component + //This is here for the Find() function from TArray + FORCEINLINE bool operator==(const FBPActorGripInformation &Other) const + { + if ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID) ) + return true; + //if (GrippedObject && GrippedObject == Other.GrippedObject) + //return true; + + return false; + } + + FORCEINLINE bool operator==(const AActor * Other) const + { + if (Other && GrippedObject && GrippedObject == (const UObject*)Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const UPrimitiveComponent * Other) const + { + if (Other && GrippedObject && GrippedObject == (const UObject*)Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const UObject * Other) const + { + if (Other && GrippedObject == Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const uint8& Other) const + { + if ((GripID != INVALID_VRGRIP_ID) && (GripID == Other)) + return true; + + return false; + } + + FBPActorGripInformation() : + GripID(INVALID_VRGRIP_ID), + GripTargetType(EGripTargetType::ActorGrip), + GrippedObject(nullptr), + GripCollisionType(EGripCollisionType::InteractiveCollisionWithPhysics), + GripLateUpdateSetting(EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping), + bColliding(false), + RelativeTransform(FTransform::Identity), + bIsSlotGrip(false), + GrippedBoneName(NAME_None), + SlotName(NAME_None), + GripMovementReplicationSetting(EGripMovementReplicationSettings::ForceClientSideMovement), + bIsPaused(false), + bIsPendingKill(false), + bLockHybridGrip(false), + bOriginalReplicatesMovement(false), + bOriginalGravity(false), + Damping(200.0f), + Stiffness(1500.0f), + AdditionTransform(FTransform::Identity), + GripDistance(0.0f), + bIsLocked(false), + LastLockedRotation(FRotator::ZeroRotator), + LastWorldTransform(FTransform::Identity), + bSetLastWorldTransform(false), + bSkipNextTeleportCheck(false), + bSkipNextConstraintLengthCheck(false), + CurrentLerpTime(0.f), + LerpSpeed(0.f), + OnGripTransform(FTransform::Identity), + bIsLerping(false) + { + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPGripPair +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair") + TObjectPtr HoldingController; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair") + uint8 GripID; + + FBPGripPair() : + HoldingController(nullptr), + GripID(INVALID_VRGRIP_ID) + {} + + FBPGripPair(UGripMotionControllerComponent * Controller, uint8 ID) : + HoldingController(Controller), + GripID(ID) + {} + + void Clear() + { + HoldingController = nullptr; + GripID = INVALID_VRGRIP_ID; + } + + bool IsValid() + { + return HoldingController != nullptr && GripID != INVALID_VRGRIP_ID; + } + + FORCEINLINE bool operator==(const FBPGripPair & Other) const + { + return (Other.HoldingController == HoldingController && ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID))); + } + + FORCEINLINE bool operator==(const UGripMotionControllerComponent * Other) const + { + return (Other == HoldingController); + } + + FORCEINLINE bool operator==(const uint8 & Other) const + { + return GripID == Other; + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPInterfaceProperties +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bDenyGripping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bAllowMultipleGrips; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripInterfaceTeleportBehavior OnTeleportBehavior; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bSimulateOnDrop; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripCollisionType SlotDefaultGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripCollisionType FreeDefaultGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + ESecondaryGripType SecondaryGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripLateUpdateSettings LateUpdateSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintStiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintDamping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintBreakDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface|AdvancedGripSettings") + FBPAdvGripSettings AdvancedGripSettings; + + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "VRGripInterface") + bool bIsHeld; // Set on grip notify, not net serializing + + // If this grip was ever held + bool bWasHeld;; + + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "VRGripInterface") + TArray HoldingControllers; // Set on grip notify, not net serializing + + FBPInterfaceProperties(): + bDenyGripping(false), + bAllowMultipleGrips(false), + OnTeleportBehavior(EGripInterfaceTeleportBehavior::DropOnTeleport), + bSimulateOnDrop(true), + SlotDefaultGripType(EGripCollisionType::ManipulationGrip), + FreeDefaultGripType(EGripCollisionType::ManipulationGrip), + SecondaryGripType(ESecondaryGripType::SG_None), + MovementReplicationType(EGripMovementReplicationSettings::ForceClientSideMovement), + LateUpdateSetting(EGripLateUpdateSettings::LateUpdatesAlwaysOff), + ConstraintStiffness(1500.0f), + ConstraintDamping(200.0f), + ConstraintBreakDistance(0.0f), + SecondarySlotRange(20.0f), + PrimarySlotRange(20.0f), + bIsHeld(false), + bWasHeld(false) + { + } +}; + + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPActorPhysicsHandleInformation +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadOnly, Category = "Settings") + TObjectPtr HandledObject; + uint8 GripID; + bool bIsPaused; + + FPhysicsActorHandle KinActorData2; + FPhysicsConstraintHandle HandleData2; + FLinearDriveConstraint LinConstraint; + FAngularDriveConstraint AngConstraint; + + FTransform LastPhysicsTransform; + FTransform COMPosition; + FTransform RootBoneRotation; + + bool bSetCOM; + bool bSkipResettingCom; + bool bSkipDeletingKinematicActor; + bool bInitiallySetup; + + FBPActorPhysicsHandleInformation() + { + HandledObject = nullptr; + LastPhysicsTransform = FTransform::Identity; + COMPosition = FTransform::Identity; + GripID = INVALID_VRGRIP_ID; + bIsPaused = false; + RootBoneRotation = FTransform::Identity; + bSetCOM = false; + bSkipResettingCom = false; + bSkipDeletingKinematicActor = false; + bInitiallySetup = false; + KinActorData2 = nullptr; + } + + FORCEINLINE bool operator==(const FBPActorGripInformation & Other) const + { + return ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID)); + } + + FORCEINLINE bool operator==(const uint8 & Other) const + { + return ((GripID != INVALID_VRGRIP_ID) && (GripID == Other)); + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvancedPhysicsHandleAxisSettings +{ + GENERATED_BODY() +public: + /** The spring strength of the drive. Force proportional to the position error. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint, meta = (ClampMin = "0.0")) + float Stiffness; + + /** The damping strength of the drive. Force proportional to the velocity error. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint, meta = (ClampMin = "0.0")) + float Damping; + + // A multiplier to add to the stiffness that is then set as the MaxForce + // It is clamped between 0.00 and 256.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float MaxForceCoefficient; + + /** Enables/Disables position drive (orientation if using angular drive)*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint) + bool bEnablePositionDrive; + + /** Enables/Disables velocity drive (damping) (angular velocity if using angular drive) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint) + bool bEnableVelocityDrive; + + FBPAdvancedPhysicsHandleAxisSettings() + { + Stiffness = 0.f; + Damping = 0.f; + MaxForceCoefficient = 0.f; + bEnablePositionDrive = false; + bEnableVelocityDrive = false; + } + + void FillFrom(FConstraintDrive& ConstraintDrive) + { + Damping = ConstraintDrive.Damping; + Stiffness = ConstraintDrive.Stiffness; + MaxForceCoefficient = ConstraintDrive.MaxForce / Stiffness; + bEnablePositionDrive = ConstraintDrive.bEnablePositionDrive; + bEnableVelocityDrive = ConstraintDrive.bEnableVelocityDrive; + } + + void FillTo(FConstraintDrive& ConstraintDrive) const + { + ConstraintDrive.Damping = Damping; + ConstraintDrive.Stiffness = Stiffness; + ConstraintDrive.MaxForce = MaxForceCoefficient * Stiffness; + ConstraintDrive.bEnablePositionDrive = bEnablePositionDrive; + ConstraintDrive.bEnableVelocityDrive = bEnableVelocityDrive; + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvancedPhysicsHandleSettings +{ + GENERATED_BODY() +public: + + // The settings for the XAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings XAxisSettings; + + // The settings for the YAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings YAxisSettings; + + // The settings for the ZAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings ZAxisSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings SlerpSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings TwistSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings SwingSettings; + + + // FConstraintSettings // settings for various things like distance limits + // Add a deletegate bindable in the motion controller + + bool FillFrom(FBPActorPhysicsHandleInformation* HandleInfo) + { + if (!HandleInfo) + return false; + + XAxisSettings.FillFrom(HandleInfo->LinConstraint.XDrive); + YAxisSettings.FillFrom(HandleInfo->LinConstraint.YDrive); + ZAxisSettings.FillFrom(HandleInfo->LinConstraint.ZDrive); + + SlerpSettings.FillFrom(HandleInfo->AngConstraint.SlerpDrive); + TwistSettings.FillFrom(HandleInfo->AngConstraint.TwistDrive); + SwingSettings.FillFrom(HandleInfo->AngConstraint.SwingDrive); + + return true; + } + + bool FillTo(FBPActorPhysicsHandleInformation* HandleInfo) const + { + if (!HandleInfo) + return false; + + XAxisSettings.FillTo(HandleInfo->LinConstraint.XDrive); + YAxisSettings.FillTo(HandleInfo->LinConstraint.YDrive); + ZAxisSettings.FillTo(HandleInfo->LinConstraint.ZDrive); + + if ((SlerpSettings.bEnablePositionDrive || SlerpSettings.bEnableVelocityDrive)) + { + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + SlerpSettings.FillTo(HandleInfo->AngConstraint.SlerpDrive); + } + else + { + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::TwistAndSwing; + TwistSettings.FillTo(HandleInfo->AngConstraint.TwistDrive); + SwingSettings.FillTo(HandleInfo->AngConstraint.SwingDrive); + } + + return true; + } +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h new file mode 100644 index 0000000..51d1850 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h @@ -0,0 +1,647 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRBaseCharacterMovementComponent.h" +#include "ReplicatedVRCameraComponent.h" +#include "GameFramework/Character.h" +#include "Navigation/PathFollowingComponent.h" +#include "VRBaseCharacter.generated.h" + +class AVRPlayerController; +class UGripMotionControllerComponent; +class UParentRelativeAttachmentComponent; +class AController; + +DECLARE_LOG_CATEGORY_EXTERN(LogBaseVRCharacter, Log, All); + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRSeatThresholdChangedSignature, bool, bIsWithinThreshold, float, ToThresholdScaler); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRPlayerStateReplicatedSignature, const APlayerState *, NewPlayerState); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPlayerTeleportedSignature); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPlayerNetworkCorrectedSignature); + +USTRUCT() +struct VREXPANSIONPLUGIN_API FRepMovementVRCharacter : public FRepMovement +{ + GENERATED_BODY() + +public: + + FRepMovementVRCharacter(); + + UPROPERTY(Transient) + bool bJustTeleported; + + UPROPERTY(Transient) + bool bJustTeleportedGrips; + + UPROPERTY(Transient) + bool bPausedTracking; + + UPROPERTY(Transient) + FVector_NetQuantize100 PausedTrackingLoc; + + UPROPERTY(Transient) + float PausedTrackingRot; + + UPROPERTY(Transient) + TObjectPtr Owner; + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + FRepMovement BaseSettings = Owner ? Owner->GetReplicatedMovement() : FRepMovement(); + + // pack bitfield with flags + uint8 Flags = (bSimulatedPhysicSleep << 0) | (bRepPhysics << 1) | (bJustTeleported << 2) | (bJustTeleportedGrips << 3) | (bPausedTracking << 4); + Ar.SerializeBits(&Flags, 5); + bSimulatedPhysicSleep = (Flags & (1 << 0)) ? 1 : 0; + bRepPhysics = (Flags & (1 << 1)) ? 1 : 0; + bJustTeleported = (Flags & (1 << 2)) ? 1 : 0; + bJustTeleportedGrips = (Flags & (1 << 3)) ? 1 : 0; + bPausedTracking = (Flags & (1 << 4)) ? 1 : 0; + + bOutSuccess = true; + + if (bPausedTracking) + { + bOutSuccess &= PausedTrackingLoc.NetSerialize(Ar, Map, bOutSuccess); + + uint16 Yaw = 0; + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(PausedTrackingRot); + Ar << Yaw; + } + else + { + Ar << Yaw; + PausedTrackingRot = Yaw; + } + + } + + // update location, rotation, linear velocity + bOutSuccess &= SerializeQuantizedVector(Ar, Location, BaseSettings.LocationQuantizationLevel); + + switch (BaseSettings.RotationQuantizationLevel) + { + case ERotatorQuantization::ByteComponents: + { + Rotation.SerializeCompressed(Ar); + break; + } + + case ERotatorQuantization::ShortComponents: + { + Rotation.SerializeCompressedShort(Ar); + break; + } + } + + bOutSuccess &= SerializeQuantizedVector(Ar, LinearVelocity, BaseSettings.VelocityQuantizationLevel); + + // update angular velocity if required + if (bRepPhysics) + { + bOutSuccess &= SerializeQuantizedVector(Ar, AngularVelocity, BaseSettings.VelocityQuantizationLevel); + } + + return true; + } +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +USTRUCT(Blueprintable) +struct VREXPANSIONPLUGIN_API FVRSeatedCharacterInfo +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + bool bSitting; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + bool bZeroToHead; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + FTransform_NetQuantize StoredTargetTransform; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + FTransform_NetQuantize InitialRelCameraTransform; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + TObjectPtr SeatParent; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + EVRConjoinedMovementModes PostSeatedMovementMode; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category = "CharacterSeatInfo", meta = (ClampMin = "1.000", UIMin = "1.000", ClampMax = "256.000", UIMax = "256.000")) + float AllowedRadius; + UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category = "CharacterSeatInfo", meta = (ClampMin = "1.000", UIMin = "1.000", ClampMax = "256.000", UIMax = "256.000")) + float AllowedRadiusThreshold; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "CharacterSeatInfo") + float CurrentThresholdScaler; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "CharacterSeatInfo") + bool bIsOverThreshold; + + bool bWasSeated; + bool bOriginalControlRotation; + bool bWasOverLimit; + + FVRSeatedCharacterInfo() + { + Clear(); + } + + void Clear() + { + bSitting = false; + bIsOverThreshold = false; + bWasOverLimit = false; + bZeroToHead = true; + StoredTargetTransform = FTransform::Identity; + InitialRelCameraTransform = FTransform::Identity; + bWasSeated = false; + bOriginalControlRotation = false; + AllowedRadius = 40.0f; + AllowedRadiusThreshold = 20.0f; + CurrentThresholdScaler = 0.0f; + SeatParent = nullptr; + PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking; + } + + void ClearTempVals() + { + bWasOverLimit = false; + bWasSeated = false; + bOriginalControlRotation = false; + CurrentThresholdScaler = 0.0f; + } + + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + Ar.SerializeBits(&bSitting, 1); + Ar.SerializeBits(&bZeroToHead, 1); + + if (bSitting) + { + InitialRelCameraTransform.NetSerialize(Ar, Map, bOutSuccess); + + // Forcing a maximum value here so that we can compress it by making assumptions + // 256 max value = 8 bits + 1 bit for sign + 7 bits for precision (up to 128 on precision, so full range 2 digit precision). + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(AllowedRadius, Ar); + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(AllowedRadiusThreshold, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(AllowedRadius, Ar); + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(AllowedRadiusThreshold, Ar); + } + } + + StoredTargetTransform.NetSerialize(Ar, Map, bOutSuccess); + Ar << SeatParent; + Ar << PostSeatedMovementMode; + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRSeatedCharacterInfo > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRReplicatedCapsuleHeight +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + float CapsuleHeight; + + FVRReplicatedCapsuleHeight() : + CapsuleHeight(0.0f) + {} + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + // Forcing a maximum value here so that we can compress it by making assumptions + // 1024 max value = 10 bits + 1 bit for sign + 7 bits for precision (up to 128 on precision, so full range 2 digit precision). + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRReplicatedCapsuleHeight > : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithNetSerializer = true + }; +}; + +UCLASS() +class VREXPANSIONPLUGIN_API AVRBaseCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + AVRBaseCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** BaseVR Character movement component belongs to */ + UPROPERTY(Transient, DuplicateTransient) + AVRPlayerController* OwningVRPlayerController; + + // If true then we will retain roomscale tracking in relative space of the character. + // If false than the movement component will offset to the hmd tracking and the tracking will be nulled out + UPROPERTY(Category = VRBaseCharacter, EditAnywhere, BlueprintReadOnly) + bool bRetainRoomscale = true; + + //virtual void CacheInitialMeshOffset(FVector MeshRelativeLocation, FRotator MeshRelativeRotation) override; + virtual void PostInitializeComponents() override; + + virtual void PossessedBy(AController* NewController); + virtual void OnRep_Controller() override; + virtual void OnRep_PlayerState() override; + + /** Used for replication of our RootComponent's position and velocity */ + UPROPERTY(ReplicatedUsing = OnRep_ReplicatedMovement) + struct FRepMovementVRCharacter ReplicatedMovementVR; + + bool bFlagTeleported; + bool bFlagTeleportedGrips; + bool bTrackingPaused; + FVector PausedTrackingLoc; + float PausedTrackingRot; + + + // Injecting our custom teleport notification + virtual void OnRep_ReplicatedMovement() override; + virtual void GatherCurrentMovement() override; + + // Give my users direct access to an event for when the player has teleported + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerTeleportedSignature OnCharacterTeleported_Bind; + + // Give my users direct access to an event for when the player has been network corrected + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerNetworkCorrectedSignature OnCharacterNetworkCorrected_Bind; + + // Give my users direct access to an event for when the player state has changed + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerStateReplicatedSignature OnPlayerStateReplicated_Bind; + + //These functions are now housed in the base character and used when possible, it saves about 7 bits of packet header overhead per send. + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformCamera(FBPVRComponentPosRep NewTransform); + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformLeftController(FBPVRComponentPosRep NewTransform); + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformRightController(FBPVRComponentPosRep NewTransform); + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // If true will replicate the capsule height on to clients, allows for dynamic capsule height changes in multiplayer + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRBaseCharacter") + bool VRReplicateCapsuleHeight; + + // OnlyReplicated to simulated clients + UPROPERTY(Replicated, ReplicatedUsing = OnRep_CapsuleHeight) + FVRReplicatedCapsuleHeight ReplicatedCapsuleHeight; + + UFUNCTION() + void OnRep_CapsuleHeight(); + + // Override this in c++ or blueprints to pass in an IK mesh to be used in some optimizations + // May be extended in the future + //UFUNCTION(BlueprintNativeEvent, Category = "BaseVRCharacter") + //USkeletalMeshComponent * GetIKMesh() const; + //virtual USkeletalMeshComponent * GetIKMesh_Implementation() const;1 + // #TODO: Work with the above, can do multiple things with it + + + // Called when the client is in climbing mode and is stepped up onto a platform + // Generally you should drop the climbing at this point and go into falling movement. + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRMovement") + void OnClimbingSteppedUp(); + virtual void OnClimbingSteppedUp_Implementation(); + + /** + * Event for adding to the climbing movement mode. Called by CharacterMovement if MovementMode is set to Climbing. + * @note C++ code should override PhysCustom_Climbing() instead. + */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "UpdateLowGravMovement", ScriptName = "UpdateLowGravMovement")) + void UpdateLowGravMovement(float DeltaTime); + virtual void UpdateLowGravMovement_Implementation(float DeltaTime) {} // Do nothing by default + + /** + * Event for adding to the climbing movement mode. Called by CharacterMovement if MovementMode is set to Climbing. + * @note C++ code should override PhysCustom_Climbing() instead. + */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "UpdateClimbingMovement", ScriptName = "UpdateClimbingMovement")) + void UpdateClimbingMovement(float DeltaTime); + virtual void UpdateClimbingMovement_Implementation(float DeltaTime){} // Do nothing by default + + // This is the offset location of the player, use this for when checking against player transform instead of the actors transform + UPROPERTY(BlueprintReadOnly, Transient, Category = "VRExpansionLibrary") + FTransform OffsetComponentToWorld; + + // Gets the forward vector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRForwardVector() const + { + return OffsetComponentToWorld.GetRotation().GetForwardVector(); + } + + // Gets the right vector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRRightVector() const + { + return OffsetComponentToWorld.GetRotation().GetRightVector(); + } + + // Gets the upvector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRUpVector() const + { + return OffsetComponentToWorld.GetRotation().GetUpVector(); + } + + // Gets the location of the HMD offset capsule (this retains the Capsule HalfHeight offset) + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRLocation() const + { + return OffsetComponentToWorld.GetLocation(); + } + + inline FVector GetVRLocation_Inline() const + { + return OffsetComponentToWorld.GetLocation(); + } + + // Returns the head location projected from the world offset (if applicable) + virtual FVector GetProjectedVRLocation() const; + + // Gets the rotation of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FRotator GetVRRotation() const + { + return OffsetComponentToWorld.GetRotation().Rotator(); + } + // Gets the location of the HMD, if the camera is missing then it just returns waist location instead + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations", meta = (DisplayName = "GetVRHeadLocation", ScriptName = "GetVRHeadLocation", Keywords = "position")) + FVector K2_GetVRHeadLocation() const + { + return GetVRHeadLocation(); + } + + inline FVector GetVRHeadLocation() const + { + return VRReplicatedCamera != nullptr ? VRReplicatedCamera->GetComponentLocation() : OffsetComponentToWorld.GetLocation(); + } + + + virtual FVector GetTargetLocation(AActor* RequestedBy) const override + { + return GetVRLocation_Inline(); + } + + // If true will use the experimental method of unseating that clears some movement replication options. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacter") + bool bUseExperimentalUnseatModeFix; + + UPROPERTY(BlueprintReadOnly, Replicated, EditAnywhere, Category = "Seating", ReplicatedUsing = OnRep_SeatedCharInfo) + FVRSeatedCharacterInfo SeatInformation; + + // Called when the seated mode is changed + UFUNCTION(BlueprintNativeEvent, Category = "Seating") + void OnSeatedModeChanged(bool bNewSeatedMode, bool bWasAlreadySeated); + virtual void OnSeatedModeChanged_Implementation(bool bNewSeatedMode, bool bWasAlreadySeated) {} + + // Called when the the player either transitions to/from the threshold boundry or the scaler value of being outside the boundry changes + // Can be used for warnings or screen darkening, ect + UFUNCTION(BlueprintNativeEvent, Category = "Seating") + void OnSeatThreshholdChanged(bool bIsWithinThreshold, float ToThresholdScaler); + virtual void OnSeatThreshholdChanged_Implementation(bool bIsWithinThreshold, float ToThresholdScaler) {} + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "Seating") + FVRSeatThresholdChangedSignature OnSeatThreshholdChanged_Bind; + + virtual FVector GetTargetHeightOffset() + { + return FVector::ZeroVector; + } + + void ZeroToSeatInformation() + { + + SetSeatRelativeLocationAndRotationVR(FVector::ZeroVector); + + NotifyOfTeleport(); + //LeftMotionController->PostTeleportMoveGrippedObjects(); + //RightMotionController->PostTeleportMoveGrippedObjects(); + } + + // Called from the movement component + void TickSeatInformation(float DeltaTime); + + UFUNCTION() + virtual void OnRep_SeatedCharInfo(); + + void InitSeatedModeTransition(); + + // Re-zeros the seated settings + UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation, Category = "BaseVRCharacter", meta = (DisplayName = "ReZeroSeating")) + void Server_ReZeroSeating(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead = true); + + // Snapturn update + UFUNCTION(Reliable, Server, WithValidation) + void Server_SeatedSnapTurn(float Yaw); + + // Sets seated mode on the character and then fires off an event to handle any special setup + // Target Transform is for teleport location if standing up, or relative camera location when sitting down. + // InitialRelCameraTransform is generally the relative transform of the camera at the time of requesting to sit. + // ZeroToHead places central point on head, if false it will use foot location and ignore Z values instead. + // Post Seated movement mode is the movement mode to switch too after seating is canceled, defaults to Walking and only uses it when un-seating. + UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation, Category = "BaseVRCharacter", meta = (DisplayName = "SetSeatedMode")) + void Server_SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius = 40.0f, float AllowedRadiusThreshold = 20.0f, bool bZeroToHead = true, EVRConjoinedMovementModes PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking); + + // Sets seated mode on the character and then fires off an event to handle any special setup + // Should only be called on the server / net authority + // If allowed radius is 0.0f then the seated mode does not check for radial distance anymore. + bool SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform TargetTransform, FTransform InitialRelCameraTransform, float AllowedRadius = 40.0f, float AllowedRadiusThreshold = 20.0f, bool bZeroToHead = true, EVRConjoinedMovementModes PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking); + + void SetSeatRelativeLocationAndRotationVR(FVector LocDelta); + + // Adds a rotation delta taking into account the HMD as a pivot point (also moves the actor), returns final location difference + // If bRotateAroundCapsule is true then it rotates around the offset capsule, otherwise it rotates around the camera + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector AddActorWorldRotationVR(FRotator DeltaRot, bool bUseYawOnly = true, bool bRotateAroundCapsule = true); + + // Sets the actors rotation taking into account the HMD as a pivot point (also moves the actor), returns the location difference + // bAccountForHMDRotation sets the rot to have the HMD face the given rot, if it is false it ignores the HMD rotation + // If bRotateAroundCapsule is true then it rotates around the offset capsule, otherwise it rotates around the camera + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector SetActorRotationVR(FRotator NewRot, bool bUseYawOnly = true, bool bAccountForHMDRotation = true, bool bRotateAroundCapsule = true); + + // Sets the actors rotation and location taking into account the HMD as a pivot point (also moves the actor), returns the location difference from the rotation + // If bRotateAroundCapsule is true then it rotates around the offset capsule, otherwise it rotates around the camera + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector SetActorLocationAndRotationVR(FVector NewLoc, FRotator NewRot, bool bUseYawOnly = true, bool bAccountForHMDRotation = true, bool bTeleport = false, bool bRotateAroundCapsule = true); + + // Sets the actors location taking into account the HMD as a pivot point, returns the location difference + // If SetCapsuleLocation is true then it offsets the capsule to the location, otherwise it will move the Camera itself to the location + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector SetActorLocationVR(FVector NewLoc, bool bTeleport, bool bSetCapsuleLocation = true); + + // Regenerates the base offsetcomponenttoworld that VR uses + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + virtual void RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) + {} + + // This sets the capsules height, but also regenerates the offset transform instantly + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter") + virtual void SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true); + + // This sets the capsules half height, but also regenerates the offset transform instantly + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter") + virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true); + + // This component is used with the normal character SkeletalMesh network smoothing system for simulated proxies + // It will lerp the characters components back to zero on simulated proxies after a move is complete. + // The simplest method of doing this was applying the exact same offset as the mesh gets to a base component that + // tracked objects are attached to. + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr NetSmoother; + + // This is just a helper proxy component after the net smoother to make it easier to move tracking around for people + // but still maintain the netsmoothers functionality + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr VRProxyComponent; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr VRMovementReference; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr VRReplicatedCamera; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr ParentRelativeAttachment; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr LeftMotionController; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr RightMotionController; + + /** Name of the LeftMotionController component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName LeftMotionControllerComponentName; + + /** Name of the RightMotionController component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName RightMotionControllerComponentName; + + /** Name of the VRReplicatedCamera component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName ReplicatedCameraComponentName; + + /** Name of the ParentRelativeAttachment component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName ParentRelativeAttachmentComponentName; + + /** Name of the net smoother component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName SmoothingSceneParentComponentName; + + /** Name of the vr proxy component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName VRProxyComponentName; + + /* + A helper function that offsets a given vector by the roots collision location + pass in a teleport location and it provides the correct spot for it to be at your feet + */ + UFUNCTION(BlueprintPure, Category = "VRGrip") + virtual FVector GetTeleportLocation(FVector OriginalLocation); + + // Notifies that we should teleport the both hand grips on next tick + // When called server side will automatically apply to remote clients as well. + // Owning clients get it on server correction automatically already. + UFUNCTION(BlueprintCallable, Category = "VRGrip") + virtual void NotifyOfTeleport(bool bRegisterAsTeleport = true); + + + // Event triggered when a move action is performed, this is ran just prior to PerformMovement in the character tick + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnCustomMoveActionPerformed(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags); + virtual void OnCustomMoveActionPerformed_Implementation(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags); + + // Event triggered when beginning to be pushed back from a wall + // bHadLocomotionInput means that the character was moving itself + // HmdInput is how much the HMD moved in that tick so you can compare sizes to decide what to do + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnBeginWallPushback(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput); + virtual void OnBeginWallPushback_Implementation(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput); + + // Event triggered when beginning to be pushed back from a wall + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnEndWallPushback(); + virtual void OnEndWallPushback_Implementation(); + + // Event when a navigation pathing operation has completed, auto calls stop movement for VR characters + UFUNCTION(BlueprintImplementableEvent, Category = "VRBaseCharacter|Navigation") + void ReceiveNavigationMoveCompleted(EPathFollowingResult::Type PathingResult); + + virtual void NavigationMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result); + + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + EPathFollowingStatus::Type GetMoveStatus() const; + + /** Returns true if the current PathFollowingComponent's path is partial (does not reach desired destination). */ + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + bool HasPartialPath() const; + + // Instantly stops pathing + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + void StopNavigationMovement(); + + UPROPERTY(BlueprintReadWrite, Category = AI) + TSubclassOf DefaultNavigationFilterClass; + + // An extended simple move to location with additional parameters + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation", Meta = (AdvancedDisplay = "bStopOnOverlap,bCanStrafe,bAllowPartialPath")) + virtual void ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius = -1, bool bStopOnOverlap = false, + bool bUsePathfinding = true, bool bProjectDestinationToNavigation = true, bool bCanStrafe = false, + TSubclassOf FilterClass = NULL, bool bAllowPartialPath = true); + + // Returns the current path points on the active navigation path + // Will return false / an empty result if the path following component is not active yet or the path is empty + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + bool GetCurrentNavigationPathPoints(TArray& NavigationPointList); + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h new file mode 100644 index 0000000..013b15d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h @@ -0,0 +1,443 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "CharacterMovementCompTypes.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "VRBaseCharacterMovementComponent.generated.h" + +class AVRBaseCharacter; +class AVRCharacter; +struct FAIRequestID; +struct FPathFollowingResult; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRBaseCharacterMovement, Log, All); + +/** Delegate for notification when to handle a climbing step up, will override default step up logic if is bound to. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVROnPerformClimbingStepUp, FVector, FinalStepUpLocation); + +/* +* The base class for our VR characters, contains common logic across them, not to be used directly +*/ +UCLASS() +class VREXPANSIONPLUGIN_API UVRBaseCharacterMovementComponent : public UCharacterMovementComponent +{ + GENERATED_BODY() +public: + + UVRBaseCharacterMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Default client to server move RPC data container. Can be bypassed via SetNetworkMoveDataContainer(). */ + FVRCharacterNetworkMoveDataContainer VRNetworkMoveDataContainer; + FVRCharacterMoveResponseDataContainer VRMoveResponseDataContainer; + + bool bNotifyTeleported; + + /** BaseVR Character movement component belongs to */ + UPROPERTY(Transient, DuplicateTransient) + TObjectPtr BaseVRCharacterOwner; + + virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent); + + virtual void MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector& NewAccel) override; + virtual void PerformMovement(float DeltaSeconds) override; + //virtual void ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) override; + + virtual bool ClientUpdatePositionAfterServerUpdate() override; + + // Overriding this to run the seated logic + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + // Skip force updating position if we are seated. + virtual bool ForcePositionUpdate(float DeltaTime) override; + + // When true will use the default engines behavior of setting rotation to match the clients instead of simulating rotations, this is really only here for FPS test pawns + // And non VRCharacter classes (simple character will use this) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent") + bool bUseClientControlRotation; + + // When true remote proxies will no longer attempt to estimate player moves when motion smoothing is enabled. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent|Smoothing") + bool bDisableSimulatedTickWhenSmoothingMovement; + + // When true the hmd movement injection speed is capped to the maximum movement speed + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bCapHMDMovementToMaxMovementSpeed; + + // If true then when in walking mode the character will attempt to automatically orient itself to the normal of the floor it is standing on + // Both the rotation and gravity vector will be effected. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Wall Walking") + bool bAutoOrientToFloorNormal = false; + + // If true then we will attempt to blend all gravity based floor changes as long as they are within the max walking angle of the CMC + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Wall Walking") + bool bBlendGravityFloorChanges = true; + + // The rate at which we will blend the change in rotation for wall walking + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Wall Walking") + float FloorOrientationChangeBlendRate = 25.0f; + + // Sets the value of bAutoOrientToFloorNormal in a manner that cleans up when removed + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacterMovementComponent|VRLocations") + void SetAutoOrientToFloorNormal(bool bAutoOrient, bool bRevertGravityWhenDisabled = true); + + void AutoTraceAndSetCharacterToNewGravity(FHitResult & TargetFloor, float DeltaTime); + bool SetCharacterToNewGravity(FVector NewGravityDirection, bool bOrientToNewGravity = true); + + // Adding seated transition + void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override; + + // Called when a valid climbing step up movement is found, if bound to the default auto step up is not performed to let custom step up logic happen instead. + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVROnPerformClimbingStepUp OnPerformClimbingStepUp; + + virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result); + + // Can't be inline anymore + FVector GetActorFeetLocationVR() const; + + FORCEINLINE bool HasRequestedVelocity() + { + return bHasRequestedVelocity; + } + + void SetHasRequestedVelocity(bool bNewHasRequestedVelocity); + bool IsClimbing() const; + + // Sets the crouching half height since it isn't exposed during runtime to blueprints + //UFUNCTION(BlueprintCallable, Category = "VRMovement") + // void SetCrouchedHalfHeight(float NewCrouchedHalfHeight); + + // Setting this higher will divide the wall slide effect by this value, to reduce collision sliding. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement", meta = (ClampMin = "0.0", UIMin = "0", ClampMax = "5.0", UIMax = "5")) + float VRWallSlideScaler; + + /** Custom version of SlideAlongSurface that handles different movement modes separately; namely during walking physics we might not want to slide up slopes. */ + virtual float SlideAlongSurface(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit, bool bHandleImpact) override; + + // Add in the custom replicated movement that climbing mode uses, this is a cutom vector that is applied to character movements + // on the next tick as a movement input.. + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacterMovementComponent|VRLocations") + void AddCustomReplicatedMovement(FVector Movement); + + // Clears the custom replicated movement, can be used to cancel movements if the mode changes + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacterMovementComponent|VRLocations") + void ClearCustomReplicatedMovement(); + + // Called to check if the server is performing a move action on a non controlled character + // If so then we just run the logic right away as it can't be inlined and won't be replicated + void CheckServerAuthedMoveAction(); + + // Set tracking paused for our root capsule and replicate the location to all connections + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SetTrackingPaused(bool bNewTrackingPaused); + virtual void StoreSetTrackingPaused(bool bNewTrackingPaused); + + // Perform a snap turn in line with the move action system + // If bRotateAroundCapsule is true then the rotation is around the offset capsule (neck) rather than the actual camera location + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SnapTurn(float SnapTurnDeltaYaw, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bFlagGripTeleport = false, bool bFlagCharacterTeleport = false, bool bRotateAroundCapsule = true); + + // Perform a rotation set in line with the move actions system + // This node specifically sets the FACING direction to a value, where your HMD is pointed + // If bRotateAroundCapsule is true then the rotation is around the offset capsule (neck) rather than the actual camera location + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SetRotation(float NewYaw, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bFlagGripTeleport = false, bool bFlagCharacterTeleport = false, bool bRotateAroundCapsule = true); + + // Perform a teleport in line with the move action system + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_Teleport(FVector TeleportLocation, FRotator TeleportRotation, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bSkipEncroachmentCheck = false); + + // Perform StopAllMovementImmediately in line with the move action system + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_StopAllMovement(); + + // Set the gravity direction for the character manually (optionall auto align to the new gravity) + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SetGravityDirection(FVector NewGravityDirection, bool bOrientToNewGravity); + + // Perform a custom moveaction that you define, will call the OnCustomMoveActionPerformed event in the character when processed so you can run your own logic + // Be sure to set the minimum data replication requirements for your move action in order to save on replication. + // Flags will always replicate if it is non zero + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_Custom(EVRMoveAction MoveActionToPerform, EVRMoveActionDataReq DataRequirementsForMoveAction, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags = 0); + + FVRMoveActionArray MoveActionArray; + + virtual void RegenerateOffset() {}; + + bool CheckForMoveAction(); + virtual bool DoMASnapTurn(FVRMoveActionContainer& MoveAction); + virtual bool DoMASetRotation(FVRMoveActionContainer& MoveAction); + virtual bool DoMATeleport(FVRMoveActionContainer& MoveAction); + virtual bool DoMAStopAllMovement(FVRMoveActionContainer& MoveAction); + virtual bool DoMASetGravityDirection(FVRMoveActionContainer& MoveAction); + virtual bool DoMAPauseTracking(FVRMoveActionContainer& MoveAction); + + FVector CustomVRInputVector; + FVector AdditionalVRInputVector; + FVector LastPreAdditiveVRVelocity; + bool bHadExtremeInput; + bool bApplyAdditionalVRInputVectorAsNegative; + + // Rewind the relative movement that we had with the HMD + void RewindVRRelativeMovement(); + + // Any movement above this value we will consider as have been a tracking jump and null out the movement in the character + // Raise this value higher if players are noticing freezing when moving quickly. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement", meta = (ClampMin = "0.0", UIMin = "0")) + float TrackingLossThreshold; + + // If we hit the tracking loss threshold then rewind position instead of running to the new location + // Will force the HMD to stay in its original spot prior to the tracking jump + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bHoldPositionOnTrackingLossThresholdHit; + + // Rewind the relative movement that we had with the HMD, this is exposed to Blueprint so that custom movement modes can use it to rewind prior to movement actions. + // Returns the Vector required to get back to the original position (for custom movement modes) + UFUNCTION(BlueprintCallable, Category = "VRMovement") + FVector RewindVRMovement(); + + // Gets the current CustomInputVector for use in custom movement modes + UFUNCTION(BlueprintCallable, Category = "VRMovement") + FVector GetCustomInputVector(); + + bool bWasInPushBack; + bool bIsInPushBack; + void StartPushBackNotification(FHitResult HitResult); + void EndPushBackNotification(); + + bool bJustUnseated; + + //virtual void SendClientAdjustment() override; + + virtual bool VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character & ServerData) override; + + inline void ApplyVRMotionToVelocity(float deltaTime) + { + bHadExtremeInput = false; + + if (AdditionalVRInputVector.IsNearlyZero() && CustomVRInputVector.IsNearlyZero()) + { + LastPreAdditiveVRVelocity = FVector::ZeroVector; + return; + } + + LastPreAdditiveVRVelocity = (AdditionalVRInputVector / deltaTime); // Save off pre-additive Velocity for restoration next tick + + if (LastPreAdditiveVRVelocity.SizeSquared() > FMath::Square(TrackingLossThreshold)) + { + bHadExtremeInput = true; + if (bHoldPositionOnTrackingLossThresholdHit) + { + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + } + + // Post the HMD velocity checks, add in our direct movement now + LastPreAdditiveVRVelocity += (CustomVRInputVector / deltaTime); + + Velocity += LastPreAdditiveVRVelocity; + + if (bCapHMDMovementToMaxMovementSpeed && GetReplicatedMovementMode() != EVRConjoinedMovementModes::C_MOVE_Falling) + { + if (IsExceedingMaxSpeed(GetMaxSpeed())) + { + // Force us to the max possible speed for the movement mode + Velocity = Velocity.GetSafeNormal() * GetMaxSpeed(); + } + } + } + + inline void RestorePreAdditiveVRMotionVelocity() + { + if (!LastPreAdditiveVRVelocity.IsNearlyZero()) + { + if (bHadExtremeInput) + { + // Just zero out the velocity here + Velocity = FVector::ZeroVector; + } + else + { + // This doesn't work with input in the opposing direction + /*FVector ProjectedVelocity = Velocity.ProjectOnToNormal(LastPreAdditiveVRVelocity.GetSafeNormal()); + float VelSq = ProjectedVelocity.SizeSquared(); + float AddSq = LastPreAdditiveVRVelocity.SizeSquared(); + + if (VelSq > AddSq || ProjectedVelocity.Equals(LastPreAdditiveVRVelocity, 0.1f)) + { + // Subtract velocity if we still relatively retain it in the normalized direction + Velocity -= LastPreAdditiveVRVelocity; + }*/ + + Velocity -= LastPreAdditiveVRVelocity; + } + } + + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + + virtual void PhysCustom(float deltaTime, int32 Iterations) override; + virtual void PhysCustom_Climbing(float deltaTime, int32 Iterations); + virtual void PhysCustom_LowGrav(float deltaTime, int32 Iterations); + + // Teleport grips on correction to fixup issues + virtual void OnClientCorrectionReceived(class FNetworkPredictionData_Client_Character& ClientData, float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode, FVector ServerGravityDirection) override; + + // Fix network smoothing with our default mesh back in + virtual void SimulatedTick(float DeltaSeconds) override; + + // Skip updates with rotational differences + virtual void SmoothCorrection(const FVector& OldLocation, const FQuat& OldRotation, const FVector& NewLocation, const FQuat& NewRotation) override; + + /** + * Smooth mesh location for network interpolation, based on values set up by SmoothCorrection. + * Internally this simply calls SmoothClientPosition_Interpolate() then SmoothClientPosition_UpdateVisuals(). + * This function is not called when bNetworkSmoothingComplete is true. + * @param DeltaSeconds Time since last update. + */ + virtual void SmoothClientPosition(float DeltaSeconds) override; + + /** Update mesh location based on interpolated values. */ + void SmoothClientPosition_UpdateVRVisuals(); + + // Added in 4.16 + ///* Allow custom handling when character hits a wall while swimming. */ + //virtual void HandleSwimmingWallHit(const FHitResult& Hit, float DeltaTime); + + // If true will never count a physicsbody channel component as the floor, to prevent jitter / physics problems. + // Make sure that you set simulating objects to the physics body channel if you want this to work correctly + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bIgnoreSimulatingComponentsInFloorCheck; + + // If true will run the control rotation in the CMC instead of in the player controller + // This puts the player rotation into the scoped movement (perf savings) and also ensures it is properly rotated prior to movement + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bRunControlRotationInMovementComponent; + + // They have gravity checks incorrect in here so having to override it again + virtual bool FloorSweepTest( + struct FHitResult& OutHit, + const FVector& Start, + const FVector& End, + ECollisionChannel TraceChannel, + const struct FCollisionShape& CollisionShape, + const struct FCollisionQueryParams& Params, + const struct FCollisionResponseParams& ResponseParam + ) const override; + + virtual void ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius, const FHitResult* DownwardSweepResult = NULL) const override; + + // Need to use actual capsule location for step up + virtual bool VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = nullptr); + + // Height to auto step up + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepHeight; + + /* Custom distance that is required before accepting a climbing stepup + * This is to help with cases where head wobble causes falling backwards + * Do NOT set to larger than capsule radius! + * #TODO: Port to SimpleCharacter as well + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingEdgeRejectDistance; + + // Higher values make it easier to trigger a step up onto a platform and moves you farther in to the base *DEFUNCT* + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepUpMultiplier; + + // If true will clamp the maximum movement on climbing step up to: VRClimbingStepUpMaxSize + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + bool bClampClimbingStepUp; + + // Maximum X/Y vector size to use when climbing stepping up (prevents very deep step ups from large movements). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepUpMaxSize; + + // If true will automatically set falling when a stepup occurs during climbing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + bool SetDefaultPostClimbMovementOnStepUp; + + // Max velocity on releasing a climbing grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingMaxReleaseVelocitySize; + + /* Custom distance that is required before accepting a walking stepup + * This is to help promote stepping up, engine default is 0.15f, generally you want it lower than that + * Do NOT set to larger than capsule radius! + * #TODO: Port to SimpleCharacter as well + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + float VREdgeRejectDistance; + + UFUNCTION(BlueprintCallable, Category = "VRMovement|Climbing") + void SetClimbingMode(bool bIsClimbing); + + // Default movement mode to switch to post climb ended, only used if SetDefaultPostClimbMovementOnStepUp is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + EVRConjoinedMovementModes DefaultPostClimbMovement; + + // Overloading this to handle an edge case + virtual void ApplyNetworkMovementMode(const uint8 ReceivedMode) override; + + /* + * This is called client side to make a replicated movement mode change that hits the server in the saved move. + * + * Custom Movement Mode is currently limited to 0 - 8, the index's 0 and 1 are currently used up for the plugin movement modes. + * So setting it to 0 or 1 would be Climbing, and LowGrav respectivly, this leaves 2-8 as open index's for use. + * For a total of 6 Custom movement modes past the currently implemented plugin ones. + */ + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void SetReplicatedMovementMode(EVRConjoinedMovementModes NewMovementMode); + + /* + * Call this to convert the current movement mode to a Conjoined one for reference + * + * Custom Movement Mode is currently limited to 0 - 8, the index's 0 and 1 are currently used up for the plugin movement modes. + * So setting it to 0 or 1 would be Climbing, and LowGrav respectivly, this leaves 2-8 as open index's for use. + * For a total of 6 Custom movement modes past the currently implemented plugin ones. + */ + UFUNCTION(BlueprintPure, Category = "VRMovement") + EVRConjoinedMovementModes GetReplicatedMovementMode(); + + // We use 4 bits for this so a maximum of 16 elements + EVRConjoinedMovementModes VRReplicatedMovementMode; + + FORCEINLINE void ApplyReplicatedMovementMode(EVRConjoinedMovementModes &NewMovementMode, bool bClearMovementMode = false) + { + if (NewMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX)//None) + { + if (NewMovementMode <= EVRConjoinedMovementModes::C_MOVE_MAX) + { + // Is a default movement mode, just directly set it + SetMovementMode((EMovementMode)NewMovementMode); + } + else // Is Custom + { + // Auto calculates the difference for our VR movements, index is from 0 so using climbing should get me correct index's as it is the first custom mode + SetMovementMode(EMovementMode::MOVE_Custom, (((int8)NewMovementMode - (uint8)EVRConjoinedMovementModes::C_VRMOVE_Climbing))); + } + + // Clearing it here instead now, as this way the code can inject it during PerformMovement + // Specifically used by the Climbing Step up, so that server rollbacks are supported + if(bClearMovementMode) + NewMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + } + } + + void UpdateFromCompressedFlags(uint8 Flags) override; + + FVector RoundDirectMovement(FVector InMovement) const; + + // Setting this below 1.0 will change how fast you de-accelerate when touching a wall + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|LowGrav", meta = (ClampMin = "0.0", UIMin = "0", ClampMax = "5.0", UIMax = "5")) + float VRLowGravWallFrictionScaler; + + // If true then low grav will ignore the default physics volume fluid friction, useful if you have a mix of low grav and normal movement + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|LowGrav") + bool VRLowGravIgnoresDefaultFluidFriction; +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h new file mode 100644 index 0000000..fa1e317 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h @@ -0,0 +1,51 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.generated.h" + +class UVRRootComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRCharacter, Log, All); + +UCLASS() +class VREXPANSIONPLUGIN_API AVRCharacter : public AVRBaseCharacter +{ + GENERATED_BODY() + +public: + AVRCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // Overriding teleport so that it auto calls my controllers re-positioning + virtual bool TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest = false, bool bNoCheck = false) override; + + UPROPERTY(Category = VRCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr VRRootReference; + + virtual FVector GetTargetHeightOffset() override; + + // Regenerates the base offsetcomponenttoworld that VR uses + virtual void RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) override; + virtual void SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true) override; + virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true) override; + + /* + A helper function that offsets a given vector by the roots collision location + pass in a teleport location and it provides the correct spot for it to be at your feet + */ + virtual FVector GetTeleportLocation(FVector OriginalLocation) override; + + // Returns the head location projected from the world offset (if applicable) + virtual FVector GetProjectedVRLocation() const override; + + + // Overriding to correct some nav stuff + FVector GetNavAgentLocation() const override; + + // An extended simple move to location with additional parameters + virtual void ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius = -1, bool bStopOnOverlap = false, + bool bUsePathfinding = true, bool bProjectDestinationToNavigation = true, bool bCanStrafe = false, + TSubclassOf FilterClass = NULL, bool bAllowPartialPath = true) override; + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h new file mode 100644 index 0000000..851c1d3 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h @@ -0,0 +1,335 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/EngineBaseTypes.h" +//#include "Engine/EngineTypes.h" +#include "CharacterMovementCompTypes.h" +#include "VRBaseCharacterMovementComponent.h" +#include "VRCharacterMovementComponent.generated.h" + +class FDebugDisplayInfo; +class ACharacter; +class AVRCharacter; +class UVRRootComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRCharacterMovement, Log, All); + +/** Shared pointer for easy memory management of FSavedMove_Character, for accumulating and replaying network moves. */ +//typedef TSharedPtr FSavedMovePtr; + + + +//FCharacterMoveResponseDataContainer VRMoveResponseDataContainer; + + +//============================================================================= +/** + * VRCharacterMovementComponent handles movement logic for the associated Character owner. + * It supports various movement modes including: walking, falling, swimming, flying, custom. + * + * Movement is affected primarily by current Velocity and Acceleration. Acceleration is updated each frame + * based on the input vector accumulated thus far (see UPawnMovementComponent::GetPendingInputVector()). + * + * Networking is fully implemented, with server-client correction and prediction included. + * + * @see ACharacter, UPawnMovementComponent + * @see https://docs.unrealengine.com/latest/INT/Gameplay/Framework/Pawn/Character/ + */ + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAIMoveCompletedSignature, FAIRequestID, RequestID, EPathFollowingResult::Type, Result); + +UCLASS() +class VREXPANSIONPLUGIN_API UVRCharacterMovementComponent : public UVRBaseCharacterMovementComponent +{ + GENERATED_BODY() +public: + + UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement) + TObjectPtr VRRootCapsule; + + virtual void RegenerateOffset() override; + + /** Reject sweep impacts that are this close to the edge of the vertical portion of the capsule when performing vertical sweeps, and try again with a smaller capsule. */ + static const float CLIMB_SWEEP_EDGE_REJECT_DISTANCE; + virtual bool IsWithinClimbingEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const; + virtual bool VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = nullptr) override; + + virtual bool IsWithinEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const override; + + // Allow merging movement replication (may cause issues when >10 players due to capsule location + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VRCharacterMovementComponent") + bool bAllowMovementMerging; + + // If true we will run client corrections off of the HMD location instead of actor, this is a settable value to allow backwards compatibility + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VRCharacterMovementComponent") + bool bRunClientCorrectionToHMD; + + // Higher values will cause more slide but better step up + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRCharacterMovementComponent", meta = (ClampMin = "0.01", UIMin = "0", ClampMax = "1.0", UIMax = "1")) + //float WallRepulsionMultiplier; + + /** + * Checks if new capsule size fits (no encroachment), and call CharacterOwner->OnStartCrouch() if successful. + * In general you should set bWantsToCrouch instead to have the crouch persist during movement, or just use the crouch functions on the owning Character. + * @param bClientSimulation true when called when bIsCrouched is replicated to non owned clients, to update collision cylinder and offset. + */ + virtual void Crouch(bool bClientSimulation = false) override; + + /** + * Checks if default capsule size fits (no encroachment), and trigger OnEndCrouch() on the owner if successful. + * @param bClientSimulation true when called when bIsCrouched is replicated to non owned clients, to update collision cylinder and offset. + */ + virtual void UnCrouch(bool bClientSimulation = false) override; + + /** @return true if the character is allowed to crouch in the current state. By default it is allowed when walking or falling, if CanEverCrouch() is true. */ + //virtual bool CanCrouchInCurrentState() const; + + /////////////////////////// + // Navigation Functions + /////////////////////////// + + /** Blueprint notification that we've completed the current movement request */ + //UPROPERTY(BlueprintAssignable, meta = (DisplayName = "MoveCompleted")) + //FAIMoveCompletedSignature ReceiveMoveCompleted; + + virtual FBasedPosition GetActorFeetLocationBased() const override; + + /** + * Checks to see if the current location is not encroaching blocking geometry so the character can leave NavWalking. + * Restores collision settings and adjusts character location to avoid getting stuck in geometry. + * If it's not possible, MovementMode change will be delayed until character reach collision free spot. + * @return True if movement mode was successfully changed + */ + virtual bool TryToLeaveNavWalking() override; + + + virtual void PhysNavWalking(float deltaTime, int32 Iterations) override; + virtual void ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) override; + + void PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction& ThisTickFunction) override; + void SimulateMovement(float DeltaSeconds) override; + void MoveSmooth(const FVector& InVelocity, const float DeltaSeconds, FStepDownResult* OutStepDownResult) override; + //void PerformMovement(float DeltaSeconds) override; + + /////////////////////////// + // End Navigation Functions + /////////////////////////// + + + /////////////////////////// + // Client adjustment overrides to allow for rotation + /////////////////////////// + + // Engines version of this is private for some reason, making it impossible to override the function that uses it. + TWeakObjectPtr LastServerMovementBaseVR = nullptr; + + virtual bool ShouldCorrectRotation() const override { return !bUseClientControlRotation; } + virtual void ClientHandleMoveResponse(const FCharacterMoveResponseDataContainer& MoveResponse) override; + + //virtual void SendClientAdjustment() override; + /** + * Have the server check if the client is outside an error tolerance, and queue a client adjustment if so. + * If either GetPredictionData_Server_Character()->bForceClientUpdate or ServerCheckClientError() are true, the client adjustment will be sent. + * RelativeClientLocation will be a relative location if MovementBaseUtility::UseRelativePosition(ClientMovementBase) is true, or a world location if false. + * @see ServerCheckClientError() + */ + virtual void ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& RelativeClientLocation, float ClientYaw, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode); + + /** + * Check for Server-Client disagreement in position or other movement state important enough to trigger a client correction. + * @see ServerMoveHandleClientError() + */ + virtual bool ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& ClientWorldLocation, float ClientYaw, const FVector& RelativeClientLocation, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode); + + /** Replicate position correction to client, associated with a timestamped servermove. Client will replay subsequent moves after applying adjustment. */ + virtual void ClientAdjustPositionVR_Implementation(float TimeStamp, FVector NewLoc, /*uint16 NewYaw,*/ FVector NewVel, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode, TOptional OptionalRotation = TOptional(), TOptional OptionalGravityDirection = TOptional()); + + virtual bool ClientUpdatePositionAfterServerUpdate() override; + /////////////////////////// + // Replication Functions + /////////////////////////// + + //////////////////////////////////// + // Network RPCs for movement + //////////////////////////////////// + /** + * The actual RPCs are passed to ACharacter, which wrap to the _Implementation and _Validate call here, to avoid Component RPC overhead. + * For example: + * Client: UCharacterMovementComponent::ServerMove(...) => Calls CharacterOwner->ServerMove(...) triggering RPC on server + * Server: ACharacter::ServerMove_Implementation(...) => Calls CharacterMovement->ServerMove_Implementation + * To override the client call to the server RPC (on CharacterOwner), override ServerMove(). + * To override the server implementation, override ServerMove_Implementation(). + */ + + + // Using my own as I don't want to cast the standard fsavedmove + //virtual void CallServerMove(const class FSavedMove_Character* NewMove, const class FSavedMove_Character* OldMove) override; + + virtual void ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) override; + + FNetworkPredictionData_Client* GetPredictionData_Client() const override; + FNetworkPredictionData_Server* GetPredictionData_Server() const override; + + /////////////////////////// + // End Replication Functions + /////////////////////////// + + /** + * Default UObject constructor. + */ + UVRCharacterMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual void OnRegister() override; + + float ImmersionDepth() const override; + bool CanCrouch(); + + // Don't really need to override this at all, it doesn't work that well even when fixed in VR + //void VisualizeMovement() const override; + + + /* + bool HasRootMotion() const + { + return RootMotionParams.bHasRootMotion; + }*/ + + // Had to modify this, since every frame can start in penetration (capsule component moves into wall before movement tick) + // It was throwing out the initial hit and not calling "step up", now I am only checking for penetration after adjustment but keeping the initial hit for step up. + // this makes for FAR more responsive step ups. + // I WILL need to override these for flying / swimming as well + bool SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport = ETeleportType::None); + bool SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport = ETeleportType::None); + + // This is here to force it to call the correct SafeMoveUpdatedComponent functions for floor movement + virtual void MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult) override; + + // Modify for correct location + virtual void ApplyRepulsionForce(float DeltaSeconds) override; + + // Update BaseOffset to be zero + virtual void UpdateBasedMovement(float DeltaSeconds) override; + + // Stop subtracting the capsules half height + virtual FVector GetImpartedMovementBaseVelocity() const override; + + // Cheating at the relative collision detection + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction); + + // Need to fill our capsule component variable here and override the default tick ordering + virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override; + + // Correct an offset sweep test + virtual void ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) override; + + // Always called with the capsulecomponent location, no idea why it doesn't just get it inside it already + // Had to force it within the function to use VRLocation instead. + virtual void FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult, bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult = NULL) const; + + // Need to use actual capsule location for step up + bool StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = NULL) override; + + virtual FVector GetPenetrationAdjustment(const FHitResult& Hit) const override; + + // MOVED THIS TO THE BASE VR CHARACTER MOVEMENT COMPONENT + // Also added a control variable for it there + // Skip physics channels when looking for floor + /*virtual bool FloorSweepTest( + FHitResult& OutHit, + const FVector& Start, + const FVector& End, + ECollisionChannel TraceChannel, + const struct FCollisionShape& CollisionShape, + const struct FCollisionQueryParams& Params, + const struct FCollisionResponseParams& ResponseParam + ) const override;*/ + + // Multiple changes to support relative motion and ledge sweeps + virtual void PhysWalking(float deltaTime, int32 Iterations) override; + + // Supporting the direct move injection + virtual void PhysFlying(float deltaTime, int32 Iterations) override; + + // Need to use VR location, was defaulting to actor + virtual bool ShouldCheckForValidLandingSpot(float DeltaTime, const FVector& Delta, const FHitResult& Hit) const override; + + // Overriding the physfalling because valid landing spots were computed incorrectly. + virtual void PhysFalling(float deltaTime, int32 Iterations) override; + + virtual void PhysSwimming(float deltaTime, int32 Iterations) override; + /** + * Handle start swimming functionality + * @param OldLocation - Location on last tick + * @param OldVelocity - velocity at last tick + * @param timeTick - time since at OldLocation + * @param remainingTime - DeltaTime to complete transition to swimming + * @param Iterations - physics iteration count + */ + void StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations); + + /* Swimming uses gravity - but scaled by (1.f - buoyancy) */ + float SwimVR(FVector Delta, FHitResult& Hit); + + /** Check if swimming pawn just ran into edge of the pool and should jump out. */ + virtual bool CheckWaterJump(FVector CheckPoint, FVector& WallNormal) override; + + // Shouldn't need to override this + /** Get as close to waterline as possible, staying on same side as currently. */ + //FVector FindWaterLine(FVector Start, FVector End) override; + + // Just calls find floor + /** Verify that the supplied hit result is a valid landing spot when falling. */ + //virtual bool IsValidLandingSpot(const FVector& CapsuleLocation, const FHitResult& Hit) const override; + + // Making sure that impulses are correct + virtual void CapsuleTouched(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override; + virtual void StoreSetTrackingPaused(bool bNewTrackingPaused) override; +}; + + +class VREXPANSIONPLUGIN_API FSavedMove_VRCharacter : public FSavedMove_VRBaseCharacter +{ + +public: + + virtual void SetInitialPosition(ACharacter* C); + virtual void PrepMoveFor(ACharacter* Character) override; + + FSavedMove_VRCharacter() : FSavedMove_VRBaseCharacter() + {} + +}; + +// Need this for capsule location replication +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Client_VRCharacter : public FNetworkPredictionData_Client_Character +{ +public: + FNetworkPredictionData_Client_VRCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Client_Character(ClientMovement) + { + + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRCharacter()); + } +}; + + +// Need this for capsule location replication????? +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Server_VRCharacter : public FNetworkPredictionData_Server_Character +{ +public: + + FNetworkPredictionData_Server_VRCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Server_Character(ClientMovement) + { + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRCharacter()); + } +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h new file mode 100644 index 0000000..a7e12a4 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h @@ -0,0 +1,358 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "VRBPDatatypes.h" +#include "VRExpansionFunctionLibrary.generated.h" + +class USplineComponent; +class UPrimitiveComponent; +class UGripMotioncontroller; +struct FGameplayTag; +struct FGameplayTagContainer; + + +enum class EControllerHand : uint8; +enum class EBPHMDDeviceType : uint8; + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(VRExpansionFunctionLibraryLog, Log, All); + +/** +* Redefining this for blueprint as it wasn't declared as BlueprintType +* Stores if the user is wearing the HMD or not. For HMDs without a sensor to detect the user wearing it, the state defaults to Unknown. +*/ +UENUM(BlueprintType) +enum class EBPHMDWornState : uint8 +{ + Unknown UMETA(DisplayName = "Unknown"), + Worn UMETA(DisplayName = "Worn"), + NotWorn UMETA(DisplayName = "Not Worn"), +}; + +UCLASS()//, meta = (BlueprintSpawnableComponent)) +class VREXPANSIONPLUGIN_API UVRExpansionFunctionLibrary : public UBlueprintFunctionLibrary +{ + //GENERATED_BODY() + GENERATED_BODY() + //~UVRExpansionFunctionLibrary(); +public: + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static UGameViewportClient * GetGameViewportClient(UObject* WorldContextObject); + + + // Sets two primitive components to ignore collision between two specific bodies + // If bAddChildBones is true then it will also add all child bones of the given bone (or the entire skeleton if no name is given) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void SetObjectsIgnoreCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1 = nullptr, FName OptionalBoneName1 = NAME_None, bool bAddChildBones1 = false, UPrimitiveComponent* Prim2 = nullptr, FName OptionalBoneName2 = NAME_None, bool bAddChildBones2 = false, bool bIgnoreCollision = true); + + // Sets two actors to entirely ignore collision between them + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void SetActorsIgnoreAllCollision(UObject* WorldContextObject, AActor* Actor1 = nullptr, AActor* Actor2 = nullptr, bool bIgnoreCollision = true); + + // Removes all collision ignore matches for the given primitive object + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void RemoveObjectCollisionIgnore(UObject* WorldContextObject, UPrimitiveComponent* Prim1); + + // Removes all collision ignore matches for the given actor + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void RemoveActorCollisionIgnore(UObject* WorldContextObject, AActor* Actor1); + + // Returns if the component is ignoring any collisions + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static bool IsComponentIgnoringCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1); + + // Returns if the component is ignoring collisions with the specific component + // This does not check per bone, but rather at scale if any part of them are ignoring each other + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static bool AreComponentsIgnoringCollisions(UObject* WorldContextObject, UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2); + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHandFromMotionSourceName")) + static bool GetHandFromMotionSourceName(FName MotionSource, EControllerHand& Hand); + + // Gets the unwound yaw of the HMD + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHMDPureYaw")) + static FRotator GetHMDPureYaw(FRotator HMDRotation); + + inline static FRotator GetHMDPureYaw_I(FRotator HMDRotation) + { + // Took this from UnityVRToolkit, no shame, I liked it + FRotationMatrix HeadMat(HMDRotation); + FVector forward = HeadMat.GetScaledAxis(EAxis::X); + FVector forwardLeveled = forward; + forwardLeveled.Z = 0; + forwardLeveled.Normalize(); + FVector mixedInLocalForward = HeadMat.GetScaledAxis(EAxis::Z); + + if (forward.Z > 0) + { + mixedInLocalForward = -mixedInLocalForward; + } + + mixedInLocalForward.Z = 0; + mixedInLocalForward.Normalize(); + float dot = FMath::Clamp(FVector::DotProduct(forwardLeveled, forward), 0.0f, 1.0f); + FVector finalForward = FMath::Lerp(mixedInLocalForward, forwardLeveled, dot * dot); + + return FRotationMatrix::MakeFromXZ(finalForward, FVector::UpVector).Rotator(); + } + + // Applies a delta rotation around a pivot point, if bUseOriginalYawOnly is true then it only takes the original Yaw into account (characters) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "RotateAroundPivot")) + static void RotateAroundPivot(FRotator RotationDelta, FVector OriginalLocation, FRotator OriginalRotation, FVector PivotPoint, FVector & NewLocation, FRotator & NewRotation,bool bUseOriginalYawOnly = true) + { + if (bUseOriginalYawOnly) + { + // Keep original pitch/roll + NewRotation.Pitch = OriginalRotation.Pitch; + NewRotation.Roll = OriginalRotation.Roll; + + // Throw out pitch/roll before calculating offset + OriginalRotation.Roll = 0; + OriginalRotation.Pitch = 0; + + // Offset to pivot point + NewLocation = OriginalLocation + OriginalRotation.RotateVector(PivotPoint); + + // Combine rotations + OriginalRotation.Yaw = (OriginalRotation.Quaternion() * RotationDelta.Quaternion()).Rotator().Yaw; + NewRotation.Yaw = OriginalRotation.Yaw; + + // Remove pivot point offset + NewLocation -= OriginalRotation.RotateVector(PivotPoint); + + } + else + { + NewLocation = OriginalLocation + OriginalRotation.RotateVector(PivotPoint); + NewRotation = (OriginalRotation.Quaternion() * RotationDelta.Quaternion()).Rotator(); + NewLocation -= NewRotation.RotateVector(PivotPoint); + } + } + + // Returns a delta rotation to have Vec1 point towards Vec2, assumes that the v + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "FindBetween")) + static FRotator BPQuat_FindBetween(FVector Vec1, FVector Vec2) + { + return FQuat::FindBetween(Vec1, Vec2).Rotator(); + } + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsHMDConnected")) + static bool GetIsHMDConnected(); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsHMDWorn")) + static EBPHMDWornState GetIsHMDWorn(); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHMDType")) + static EBPHMDDeviceType GetHMDType(); + + // Gets whether the game is running in VRPreview or is a non editor build game (returns true for either). + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "IsInVREditorPreviewOrGame")) + static bool IsInVREditorPreviewOrGame(); + + // Gets whether the game is running in VRPreview + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "IsInVREditorPreview")) + static bool IsInVREditorPreview(); + + /** + * Finds the minimum area rectangle that encloses all of the points in InVerts + * Engine default version is server only for some reason + * Uses algorithm found in http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf + * + * @param InVerts - Points to enclose in the rectangle + * @outparam OutRectCenter - Center of the enclosing rectangle + * @outparam OutRectSideA - Vector oriented and sized to represent one edge of the enclosing rectangle, orthogonal to OutRectSideB + * @outparam OutRectSideB - Vector oriented and sized to represent one edge of the enclosing rectangle, orthogonal to OutRectSideA + */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void NonAuthorityMinimumAreaRectangle(UObject* WorldContextObject, const TArray& InVerts, const FVector& SampleSurfaceNormal, FVector& OutRectCenter, FRotator& OutRectRotation, float& OutSideLengthX, float& OutSideLengthY, bool bDebugDraw = false); + + // A Rolling average low pass filter + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "LowPassFilter_RollingAverage")) + static void LowPassFilter_RollingAverage(FVector lastAverage, FVector newSample, FVector & newAverage, int32 numSamples = 10); + + // A exponential low pass filter + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "LowPassFilter_Exponential")) + static void LowPassFilter_Exponential(FVector lastAverage, FVector newSample, FVector & newAverage, float sampleFactor = 0.25f); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsActorMovable")) + static bool GetIsActorMovable(AActor * ActorToCheck); + + // Gets if an actors root component contains a grip slot within range + UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlotInRangeByTypeName")) + static void GetGripSlotInRangeByTypeName(FName SlotType, AActor * Actor, FVector WorldLocation, float MaxRange, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent* QueryController = nullptr); + + // Gets if an actors root component contains a grip slot within range + UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlotInRangeByTypeName_Component")) + static void GetGripSlotInRangeByTypeName_Component(FName SlotType, USceneComponent * Component, FVector WorldLocation, float MaxRange, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent* QueryController = nullptr); + + /* Returns true if the values are equal (A == B) */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equal VR Grip", CompactNodeTitle = "==", Keywords = "== equal"), Category = "VRExpansionFunctions") + static bool EqualEqual_FBPActorGripInformation(const FBPActorGripInformation &A, const FBPActorGripInformation &B); + + /* Returns true if the grip is active (both valid and not paused) */ + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions") + static bool IsActiveGrip(const FBPActorGripInformation& Grip); + + /** Make a transform net quantize from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|TransformNetQuantize") + static FTransform_NetQuantize MakeTransform_NetQuantize(FVector Location, FRotator Rotation, FVector Scale); + + /** Breaks apart a transform net quantize into location, rotation and scale */ + UFUNCTION(BlueprintPure, Category = "VRExpansionLibrary|TransformNetQuantize", meta = (NativeBreakFunc)) + static void BreakTransform_NetQuantize(const FTransform_NetQuantize& InTransform, FVector& Location, FRotator& Rotation, FVector& Scale); + + /** Converts a FTransform into a FTransform_NetQuantize */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToTransform_NetQuantize (Transform)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|TransformNetQuantize") + static FTransform_NetQuantize Conv_TransformToTransformNetQuantize(const FTransform &InTransform); + + /** Converts a vector into a FVector_NetQuantize */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize Conv_FVectorToFVectorNetQuantize(const FVector &InVector); + + /** Make a transform net quantize from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize MakeVector_NetQuantize(FVector InVector); + + /** Converts a vector into a FVector_NetQuantize10 */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize10 (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize10 Conv_FVectorToFVectorNetQuantize10(const FVector &InVector); + + /** Make a transform net quantize10 from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize10 MakeVector_NetQuantize10(FVector InVector); + + /** Converts a vector into a FVector_NetQuantize100 */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize100 (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize100 Conv_FVectorToFVectorNetQuantize100(const FVector &InVector); + + /** Make a transform net quantize100 from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize100 MakeVector_NetQuantize100(FVector InVector); + + /** Converts a FBPGripPair into a MotionController */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToController (FBPGripPair)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary") + static UGripMotionControllerComponent * Conv_GripPairToMotionController(const FBPGripPair &GripPair); + + /** Converts a FBPGripPair into a GripID */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToGripID (FBPGripPair)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary") + static uint8 Conv_GripPairToGripID(const FBPGripPair &GripPair); + + // Adds a USceneComponent Subclass, that is based on the passed in Class, and added to the Outer(Actor) object + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Scene Component By Class"), Category = "VRExpansionLibrary") + static USceneComponent* AddSceneComponentByClass(UObject* Outer, TSubclassOf Class, const FTransform & ComponentRelativeTransform); + + + /** Resets a Filter so that the first time it is used again it is clean */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static void ResetPeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter); + + /** Adds an entry to the Peak low pass filter */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static void UpdatePeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter, FVector NewSample); + + /** Gets the peak value of the Peak Low Pass Filter */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static FVector GetPeak_PeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter); + + + /** Resets a Euro Low Pass Filter so that the first time it is used again it is clean */ + UFUNCTION(BlueprintCallable, Category = "EuroLowPassFilter") + static void ResetEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter); + + /** Runs the smoothing function of a Euro Low Pass Filter */ + UFUNCTION(BlueprintCallable, Category = "EuroLowPassFilter") + static void RunEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter, FVector InRawValue, const float DeltaTime, FVector & SmoothedValue); + + // Applies the same laser smoothing that the vr editor uses to an array of points + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Smooth Update Laser Spline"), Category = "VRExpansionLibrary") + static void SmoothUpdateLaserSpline(USplineComponent * LaserSplineComponent, TArray LaserSplineMeshComponents, FVector InStartLocation, FVector InEndLocation, FVector InForward, float LaserRadius); + + /** + * Determine if any tag in the BaseContainer matches against any tag in OtherContainer with a required direct parent for both + * + * @param TagParent Required direct parent tag + * @param BaseContainer Container containing values to check + * @param OtherContainer Container to check against. + * + * @return True if any tag was found that matches any tags explicitly present in OtherContainer with the same DirectParent + */ + UFUNCTION(BlueprintPure, Category = "GameplayTags") + static bool MatchesAnyTagsWithDirectParentTag(FGameplayTag DirectParentTag,const FGameplayTagContainer& BaseContainer, const FGameplayTagContainer& OtherContainer); + + /** + * Determine if any tag in the BaseContainer has the exact same direct parent tag and returns the first one + * @param TagParent Required direct parent tag + * @param BaseContainer Container containing values to check + + * @return True if any tag was found and also returns the tag + */ + UFUNCTION(BlueprintPure, Category = "GameplayTags") + static bool GetFirstGameplayTagWithExactParent(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, FGameplayTag& FoundTag); + + // #TODO: probably need to implement this some day + // This doesn't work for the web browser widget but it does for the normal widgets like text boxes + // Just have to SetKeyboardFocus or SetUserFocus for the input widget first + // Main problem is it takes focus from the player then.... + /*UFUNCTION(BlueprintCallable, Category = "KeyboardSimulation") + static void SimulateCharacterEntry(UPARAM(ref) const FString& InChar) + { + + for (int32 CharIndex = 0; CharIndex < InChar.Len(); CharIndex++) + { + TCHAR CharKey = InChar[CharIndex]; + const bool bRepeat = false; + FCharacterEvent CharacterEvent(CharKey, FModifierKeysState(), 0, bRepeat); + FSlateApplication::Get().ProcessKeyCharEvent(CharacterEvent); + } + + }*/ + /* + void FVREditorActionCallbacks::SimulateBackspace() + { + // Slate editable text fields handle backspace as a character entry + FString BackspaceString = FString(TEXT("\b")); + bool bRepeat = false; + SimulateCharacterEntry(BackspaceString); + } + + void FVREditorActionCallbacks::SimulateKeyDown(const FKey Key, const bool bRepeat) + { + const uint32* KeyCodePtr; + const uint32* CharCodePtr; + FInputKeyManager::Get().GetCodesFromKey(Key, KeyCodePtr, CharCodePtr); + + uint32 KeyCode = KeyCodePtr ? *KeyCodePtr : 0; + uint32 CharCode = CharCodePtr ? *CharCodePtr : 0; + + FKeyEvent KeyEvent(Key, FModifierKeysState(), 0, bRepeat, KeyCode, CharCode); + bool DownResult = FSlateApplication::Get().ProcessKeyDownEvent(KeyEvent); + + if (CharCodePtr) + { + FCharacterEvent CharacterEvent(CharCode, FModifierKeysState(), 0, bRepeat); + FSlateApplication::Get().ProcessKeyCharEvent(CharacterEvent); + } + } + + void FVREditorActionCallbacks::SimulateKeyUp(const FKey Key) + { + const uint32* KeyCodePtr; + const uint32* CharCodePtr; + FInputKeyManager::Get().GetCodesFromKey(Key, KeyCodePtr, CharCodePtr); + + uint32 KeyCode = KeyCodePtr ? *KeyCodePtr : 0; + uint32 CharCode = CharCodePtr ? *CharCodePtr : 0; + + FKeyEvent KeyEvent(Key, FModifierKeysState(), 0, false, KeyCode, CharCode); + FSlateApplication::Get().ProcessKeyUpEvent(KeyEvent); + }*/ +}; + + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h new file mode 100644 index 0000000..57fca23 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h @@ -0,0 +1,19 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + + +class FVRExpansionPluginModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + void RegisterSettings(); + + void UnregisterSettings(); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h new file mode 100644 index 0000000..1618cc8 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h @@ -0,0 +1,368 @@ + + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRBPDatatypes.h" +#include "Engine/DataAsset.h" + +//#include "Engine/EngineTypes.h" +//#include "Engine/EngineBaseTypes.h" +#include "TimerManager.h" +#include "VRGestureComponent.generated.h" + +DECLARE_STATS_GROUP(TEXT("TICKGesture"), STATGROUP_TickGesture, STATCAT_Advanced); + +class USplineMeshComponent; +class USplineComponent; +class AVRBaseCharacter; + + +UENUM(Blueprintable) +enum class EVRGestureState : uint8 +{ + GES_None, + GES_Recording, + GES_Detecting +}; + + +UENUM(Blueprintable) +enum class EVRGestureMirrorMode : uint8 +{ + GES_NoMirror, + GES_MirrorLeft, + GES_MirrorRight, + GES_MirrorBoth +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGestureSettings +{ + GENERATED_BODY() +public: + + // Minimum length to start recognizing this gesture at + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + int Minimum_Gesture_Length; + + // Maximum distance between the last observations before throwing out this gesture, raise this to make it easier to start checking this gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + float firstThreshold; + + // Full threshold before detecting the gesture, raise this to lower accuracy but make it easier to detect this gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + float FullThreshold; + + // If set to left/right, will mirror the detected gesture if the gesture component is set to match that value + // If set to Both mode, the gesture will be checked both normal and mirrored and the best match will be chosen + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + EVRGestureMirrorMode MirrorMode; + + // If enabled this gesture will be checked when inside a DB + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + bool bEnabled; + + // If enabled this gesture will have sample data scaled to it when recognizing (if false you will want to record the gesture without scaling) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + bool bEnableScaling; + + FVRGestureSettings() + { + Minimum_Gesture_Length = 1; + firstThreshold = 20.0f; + FullThreshold = 20.0f; + MirrorMode = EVRGestureMirrorMode::GES_NoMirror; + bEnabled = true; + bEnableScaling = true; + } +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGesture +{ + GENERATED_BODY() +public: + + // Name of the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FString Name; + + // Enum uint8 for end user use + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + uint8 GestureType; + + // Samples in the recorded gesture + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "VRGesture") + TArray Samples; + + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "VRGesture") + FBox GestureSize; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FVRGestureSettings GestureSettings; + + FVRGesture() + { + GestureType = 0; + GestureSize = FBox(); + } + + void CalculateSizeOfGesture(bool bAllowResizing = false, float TargetExtentSize = 1.f) + { + FVector NewSample; + for (int i = 0; i < Samples.Num(); ++i) + { + NewSample = Samples[i]; + GestureSize.Max.X = FMath::Max(NewSample.X, GestureSize.Max.X); + GestureSize.Max.Y = FMath::Max(NewSample.Y, GestureSize.Max.Y); + GestureSize.Max.Z = FMath::Max(NewSample.Z, GestureSize.Max.Z); + + GestureSize.Min.X = FMath::Min(NewSample.X, GestureSize.Min.X); + GestureSize.Min.Y = FMath::Min(NewSample.Y, GestureSize.Min.Y); + GestureSize.Min.Z = FMath::Min(NewSample.Z, GestureSize.Min.Z); + } + + if (bAllowResizing) + { + FVector BoxSize = GestureSize.GetSize(); + float Scaler = TargetExtentSize / BoxSize.GetMax(); + + for (int i = 0; i < Samples.Num(); ++i) + { + Samples[i] *= Scaler; + } + + GestureSize.Min *= Scaler; + GestureSize.Max *= Scaler; + } + } +}; + +/** +* Items Database DataAsset, here we can save all of our game items +*/ +UCLASS(BlueprintType, Category = "VRGestures") +class VREXPANSIONPLUGIN_API UGesturesDatabase : public UDataAsset +{ + GENERATED_BODY() +public: + + // Gestures in this database + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TArray Gestures; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + float TargetGestureScale; + + UGesturesDatabase() + { + TargetGestureScale = 100.0f; + } + + // Recalculate size of gestures and re-scale them to the TargetGestureScale (if bScaleToDatabase is true) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void RecalculateGestures(bool bScaleToDatabase = true); + + // Fills a spline component with a gesture, optionally also generates spline mesh components for it (uses ones already attached if possible) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void FillSplineWithGesture(UPARAM(ref)FVRGesture &Gesture, USplineComponent * SplineComponent, bool bCenterPointsOnSpline = true, bool bScaleToBounds = false, float OptionalBounds = 0.0f, bool bUseCurvedPoints = true, bool bFillInSplineMeshComponents = true, UStaticMesh * Mesh = nullptr, UMaterial * MeshMat = nullptr); + + // Imports a spline as a gesture, Segment len is the max segment length (will break lines up into lengths of this size) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + bool ImportSplineAsGesture(USplineComponent * HostSplineComponent, FString GestureName, bool bKeepSplineCurves = true, float SegmentLen = 10.0f, bool bScaleToDatabase = true); + +}; + + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGestureSplineDraw +{ + GENERATED_BODY() +public: + + UPROPERTY() + TObjectPtr SplineComponent; + + UPROPERTY() + TArray> SplineMeshes; + + int LastIndexSet; + int NextIndexCleared; + + // Marches through the array and clears the last point + void ClearLastPoint(); + + // Hides all spline meshes and re-inits the spline component + void Reset(); + + void Clear(); + + FVRGestureSplineDraw(); + + ~FVRGestureSplineDraw(); +}; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FVRGestureDetectedSignature, uint8, GestureType, FString, DetectedGestureName, int, DetectedGestureIndex, UGesturesDatabase *, GestureDataBase, FVector, OriginalUnscaledGestureSize); + +/** +* A scene component that can sample its positions to record / track VR gestures +* Core code is from https://social.msdn.microsoft.com/Forums/en-US/4a428391-82df-445a-a867-557f284bd4b1/dynamic-time-warping-to-recognize-gestures?forum=kinectsdk +* I would also like to acknowledge RuneBerg as he appears to have used the same core codebase and I discovered that halfway through implementing this +* If this algorithm should not prove stable enough I will likely look into using a more complex and faster one in the future, I have several modifications +* to the base DTW algorithm noted from a few research papers. I only implemented this one first as it was a single header file and the quickest to implement. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRGestureComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UVRGestureComponent(const FObjectInitializer& ObjectInitializer); + + + // Size of obeservations vectors. + //int dim; // Not needed, this is just dimensionality + // Can be used for arrays of samples (IE: multiple points), could add back in eventually + // if I decide to support three point tracked gestures or something at some point, but its a waste for single point. + + UFUNCTION(BlueprintImplementableEvent, Category = "BaseVRCharacter") + void OnGestureDetected(uint8 GestureType, FString &DetectedGestureName, int & DetectedGestureIndex, UGesturesDatabase * GestureDatabase, FVector OriginalUnscaledGestureSize); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FVRGestureDetectedSignature OnGestureDetected_Bind; + + // Known sequences + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr GesturesDB; + + // Tolerance within we throw out duplicate samples + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + float SameSampleTolerance; + + // If a gesture is set to match this value then detection will mirror the gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + EVRGestureMirrorMode MirroringHand; + + // Tolerance within we throw out duplicate samples + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr TargetCharacter; + + FVRGestureSplineDraw RecordingGestureDraw; + + // Should we draw splines curved or straight + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bDrawSplinesCurved; + + // If false will get the gesture in relative space instead + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bGetGestureInWorldSpace; + + // Mesh to use when drawing splines + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr SplineMesh; + + // Scaler to apply to the spline mesh components + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + FVector2D SplineMeshScaler; + + // Material to use when drawing splines + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr SplineMaterial; + + // HTZ to run recording at for detection and saving - now being used as a frame time instead of a HTZ + float RecordingDelta; + + // Number of samples to keep in memory during detection + int RecordingBufferSize; + + float RecordingClampingTolerance; + bool bRecordingFlattenGesture; + bool bDrawRecordingGesture; + bool bDrawRecordingGestureAsSpline; + bool bGestureChanged; + + // Handle to our update timer + FTimerHandle TickGestureTimer_Handle; + + // Maximum vertical or horizontal steps in a row in the lookup table before throwing out a gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + int maxSlope; + + UPROPERTY(BlueprintReadOnly, Category = "VRGestures") + EVRGestureState CurrentState; + + // Currently recording gesture + UPROPERTY(BlueprintReadOnly, Category = "VRGestures") + FVRGesture GestureLog; + + inline float GetGestureDistance(FVector Seq1, FVector Seq2, bool bMirrorGesture = false) + { + if (bMirrorGesture) + { + return FVector::DistSquared(Seq1, FVector(Seq2.X, -Seq2.Y, Seq2.Z)); + } + + return FVector::DistSquared(Seq1, Seq2); + } + + void BeginDestroy() override; + + // Recalculates a gestures size and re-scales it to the given database + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void RecalculateGestureSize(UPARAM(ref) FVRGesture & InputGesture, UGesturesDatabase * GestureDB); + + // Draw a gesture with a debug line batch + UFUNCTION(BlueprintCallable, Category = "VRGestures", meta = (WorldContext = "WorldContextObject")) + void DrawDebugGesture(UObject* WorldContextObject, UPARAM(ref)FTransform& StartTransform, FVRGesture GestureToDraw, FColor const& Color, bool bPersistentLines = false, uint8 DepthPriority = 0, float LifeTime = -1.f, float Thickness = 0.f); + + FVector StartVector; + FTransform OriginatingTransform; + FTransform ParentRelativeTransform; + + /* Function to begin recording a gesture for detection or saving + * + * bRunDetection: Should we detect gestures or only record them + * bFlattenGestue: Should we flatten the gesture into 2 dimensions (more stable detection and recording, less pretty visually) + * bDrawGesture: Should we draw the gesture during recording of it + * bDrawAsSpline: If true we will use spline meshes, if false we will draw as debug lines + * SamplingHTZ: How many times a second we will record a gesture point, recording is done with a timer now, i would steer away + * from htz > possible frames as that could cause double timer updates with how timers are implemented. + * SampleBufferSize: How many points we will store in history at a time + * ClampingTolerance: If larger than 0.0, we will clamp points to a grid of this size + */ + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void BeginRecording(bool bRunDetection, bool bFlattenGesture = true, bool bDrawGesture = true, bool bDrawAsSpline = false, int SamplingHTZ = 30, int SampleBufferSize = 60, float ClampingTolerance = 0.01f); + + // Ends recording and returns the recorded gesture + UFUNCTION(BlueprintCallable, Category = "VRGestures") + FVRGesture EndRecording(); + + // Clears the current recording + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void ClearRecording(); + + // Saves a VRGesture to the database, if Scale To Database is true then it will scale the data + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void SaveRecording(UPARAM(ref) FVRGesture &Recording, FString RecordingName, bool bScaleRecordingToDatabase = true); + + void CaptureGestureFrame(); + + // Ticks the logic from the gameplay timer. + void TickGesture(); + + + // Recognize gesture in the given sequence. + // It will always assume that the gesture ends on the last observation of that sequence. + // If the distance between the last observations of each sequence is too great, or if the overall DTW distance between the two sequences is too great, no gesture will be recognized. + void RecognizeGesture(FVRGesture inputGesture); + + + // Compute the min DTW distance between seq2 and all possible endings of seq1. + float dtw(FVRGesture seq1, FVRGesture seq2, bool bMirrorGesture = false, float Scaler = 1.f); + +}; + diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h new file mode 100644 index 0000000..dd6d171 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h @@ -0,0 +1,326 @@ +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/PlayerInput.h" +#include "GameFramework/InputSettings.h" +#include "VRBPDatatypes.h" +#include "Curves/CurveFloat.h" +#include "GripScripts/GS_Melee.h" +#include "GripScripts/GS_GunTools.h" +#include "VRGlobalSettings.generated.h" + +class UGrippableSkeletalMeshComponent; + +USTRUCT(BlueprintType, Category = "ControllerProfiles") +struct VREXPANSIONPLUGIN_API FBPVRControllerProfile +{ + GENERATED_BODY() +public: + + // Name of controller profile + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + FName ControllerName; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + FTransform_NetQuantize SocketOffsetTransform; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + bool bUseSeperateHandOffsetTransforms; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles", meta = (editcondition = "bUseSeperateHandOffsetTransforms")) + FTransform_NetQuantize SocketOffsetTransformRightHand; + + + FBPVRControllerProfile() : + ControllerName(NAME_None), + SocketOffsetTransform(FTransform::Identity), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerName) : + ControllerName(ControllerName), + SocketOffsetTransform(FTransform::Identity), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerNameIn, const FTransform & Offset) : + ControllerName(ControllerNameIn), + SocketOffsetTransform(Offset), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerNameIn, const FTransform & Offset, const FTransform & OffsetRight) : + ControllerName(ControllerNameIn), + SocketOffsetTransform(Offset), + bUseSeperateHandOffsetTransforms(true), + SocketOffsetTransformRightHand(OffsetRight) + {} + + FORCEINLINE bool operator==(const FBPVRControllerProfile &Other) const + { + return this->ControllerName == Other.ControllerName; + } +}; + +UCLASS(config = Engine, defaultconfig) +class VREXPANSIONPLUGIN_API UVRGlobalSettings : public UObject +{ + GENERATED_BODY() + +public: + UVRGlobalSettings(const FObjectInitializer& ObjectInitializer); + + // Set scaler values + void SetScalers(); + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + // The skeletal mesh component class to use for grippable characters + // If you set this to none it will fall back to the default grippable class so that it doesn't brick your project + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "Misc") + TSubclassOf DefaultGrippableCharacterMeshComponentClass; + + // Using a getter to stay safe from bricking peoples projects if they set it to none somehow + static TSubclassOf GetDefaultGrippableCharacterMeshComponentClass(); + + // If true we will use contact modification for the collision ignore subsystem + // Its more expensive but works with non simulating pairs + // #WARNING: Don't use yet EXPERIMENTAL + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|CollisionIgnore") + bool bUseCollisionModificationForCollisionIgnore; + + // Number of updates a second to use for the collision cleanup checks + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|CollisionIgnore") + float CollisionIgnoreSubsystemUpdateRate; + + // Whether we should use the physx to chaos translation scalers or not + // This should be off on native chaos projects that have been set with the correct stiffness and damping settings already + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + bool bUseChaosTranslationScalers; + + // If true we will also set the engines chaos scalers as well to equal our overrides + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + bool bSetEngineChaosScalers; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float LinearDriveStiffnessScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float LinearDriveDampingScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float AngularDriveStiffnessScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float AngularDriveDampingScale; + + // Hard joint stiffness + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointStiffness; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftLinearStiffnessScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftLinearDampingScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftAngularStiffnessScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftAngularDampingScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointLinearBreakScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointAngularBreakScale; + + // If we should lerp hybrid with sweep grips out of collision + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "HybridWithSweepLerp") + bool bLerpHybridWithSweepGrips; + + // If true we only lerp the rotation of hybrid grips + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "HybridWithSweepLerp") + bool bOnlyLerpHybridRotation; + + // If true we calculate a speed off of the lerp duration and TInterp using it + // The Hybrid with sweep lerp duration then is used to divide 1.0f (x10) to get the speed (lower duration = faster movement) + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "HybridWithSweepLerp") + bool bHybridWithSweepUseDistanceBasedLerp; + + // Duration that the typical lerp takes, for the distance based lerp this is turned into a speed value based off of 1.0f / it + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "HybridWithSweepLerp") + float HybridWithSweepLerpDuration; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + bool bUseGlobalLerpToHand; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + bool bSkipLerpToHandIfHeld; + + // If the initial grip distance is closer than this value then the lerping will not be performed. + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MinDistanceForLerp; + + // How many seconds the lerp should take + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float LerpDuration; + + // The minimum speed (in UU per second) that that the lerp should have across the initial grip distance + // Will speed the LerpSpeed up to try and maintain this initial speed if required + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MinSpeedForLerp; + + // The maximum speed (in UU per second) that the lerp should have across the initial grip distance + // Will slow the LerpSpeed down to try and maintain this initial speed if required + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MaxSpeedForLerp; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + EVRLerpInterpolationMode LerpInterpolationMode; + + // Whether to use a curve map to map the lerp to + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand|Curve") + bool bUseCurve; + + // The curve to follow when using a curve map, only uses from 0.0 - 1.0 of the curve timeline and maps it across the entire duration + UPROPERTY(config, Category = "GlobalLerpToHand|Curve", EditAnywhere, meta = (editcondition = "bUseCurve")) + FRuntimeFloatCurve OptionalCurveToFollow; + + // Alter the values of the virtual stock settings and save them out + UFUNCTION(BlueprintPure, Category = "GlobalLerpToHand") + static bool IsGlobalLerpEnabled(); + + // List of surfaces and their properties for the melee script + UPROPERTY(config, EditAnywhere, Category = "MeleeSettings") + TArray MeleeSurfaceSettings; + + // Default global virtual stock settings for the gun script + UPROPERTY(config, EditAnywhere, Category = "GunSettings") + FBPVirtualStockSettings VirtualStockSettings; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroMinCutoff; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroCutoffSlope; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroDeltaCutoff; + + // Get the values of the virtual stock settings + UFUNCTION(BlueprintCallable, Category = "MeleeSettings") + static void GetMeleeSurfaceGlobalSettings(TArray& OutMeleeSurfaceSettings); + + // Get the values of the virtual stock settings + UFUNCTION(BlueprintCallable, Category = "GunSettings|VirtualStock") + static void GetVirtualStockGlobalSettings(FBPVirtualStockSettings& OutVirtualStockSettings); + + // Alter the values of the virtual stock settings and save them out + UFUNCTION(BlueprintCallable, Category = "GunSettings|VirtualStock") + static void SaveVirtualStockGlobalSettings(FBPVirtualStockSettings NewVirtualStockSettings); + + + DECLARE_MULTICAST_DELEGATE(FVRControllerProfileChangedEvent); + /** Delegate for notification when the controller profile changes. */ + FVRControllerProfileChangedEvent OnControllerProfileChangedEvent; + + // Controller profiles to store related information on a per profile basis + UPROPERTY(config, EditAnywhere, Category = "ControllerProfiles") + TArray ControllerProfiles; + + // Store these to save some processing when getting the transform after a profile is loaded + FName CurrentControllerProfileInUse; + FTransform CurrentControllerProfileTransform; + bool bUseSeperateHandTransforms; + FTransform CurrentControllerProfileTransformRight; + + // Adjust the transform of a socket for a particular controller model, if a name is not sent in, it will use the currently loaded one + // If there is no currently loaded one, it will return the input transform as is. + // If bIsRightHand and the target profile uses seperate hand transforms it will use the right hand transform + UFUNCTION(BlueprintPure, Category = "VRControllerProfiles") + static FTransform AdjustTransformByControllerProfile(FName OptionalControllerProfileName, const FTransform& SocketTransform, bool bIsRightHand = false); + + // Adjust the transform of a socket for a particular controller model + // If there is no currently loaded one, it will return the input transform as is. + // If bIsRightHand and the target profile uses seperate hand transforms it will use the right hand transform + UFUNCTION(BlueprintPure, Category = "VRControllerProfiles") + static FTransform AdjustTransformByGivenControllerProfile(UPARAM(ref) FBPVRControllerProfile& ControllerProfile, const FTransform& SocketTransform, bool bIsRightHand = false); + + // Get array of controller profiles + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static TArray GetControllerProfiles(); + + // Overwrite a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void OverwriteControllerProfile(UPARAM(ref)FBPVRControllerProfile& OverwritingProfile, bool bSaveOutToConfig = true); + + // Add a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void AddControllerProfile(UPARAM(ref)FBPVRControllerProfile& NewProfile, bool bSaveOutToConfig = true); + + // Overwrite a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void DeleteControllerProfile(FName ControllerProfileName, bool bSaveOutToConfig = true); + + // Saved the VRGlobalSettings out to its config file, will include any alterations that you made to the profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void SaveControllerProfiles(); + + + // Get name of currently loaded profile (if one is loaded) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static FName GetCurrentProfileName(bool& bHadLoadedProfile); + + // Get name of currently loaded profile (if one is loaded) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static FBPVRControllerProfile GetCurrentProfile(bool& bHadLoadedProfile); + + // Get a controller profile by name + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool GetControllerProfile(FName ControllerProfileName, FBPVRControllerProfile& OutProfile); + + // Load a controller profile by name + // Action / Axis mappings overwrite ones with the same action/axis name in the input settings. + // If you have an action/axis override but don't assign buttons to it then it will just delete it. + // This can be useful for removing actions and using multiple actions (IE: Grip Touch, Grip Vive actions) + // For when just changing the buttons isn't good enough + // If bSetDefaults is true, will set this as the currently loaded profile + // Otherwise will just load it (useful for Left/Right handed profile additions and the like to have it false) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool LoadControllerProfileByName(FName ControllerProfileName, bool bSetAsCurrentProfile = true); + + // Load a controller profile + // Action / Axis mappings overwrite ones with the same action/axis name in the input settings. + // If you have an action/axis override but don't assign buttons to it then it will just delete it. + // This can be useful for removing actions and using multiple actions (IE: Grip Touch, Grip Vive actions) + // For when just changing the buttons isn't good enough + // If bSetDefaults is true, will set this as the currently loaded profile + // Otherwise will just load it (useful for Left/Right handed profile additions and the like to have it false) + // AS OF 4.25 AXIS and ACTION mappings do nothing, will be deleting around 4.26 #TODO: Delete around 4.26 + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool LoadControllerProfile(const FBPVRControllerProfile& ControllerProfile, bool bSetAsCurrentProfile = true); + + virtual void PostInitProperties() override; +}; diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h new file mode 100644 index 0000000..67a5db1 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h @@ -0,0 +1,160 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "UObject/ObjectMacros.h" +//#include "UObject/ScriptMacros.h" +#include "VRBPDatatypes.h" +#include "InputCoreTypes.h" +#include "Engine/EngineBaseTypes.h" +#include "UObject/Interface.h" + +#include "VRGripInterface.generated.h" + +// Forward declare +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +UINTERFACE(Blueprintable) +class VREXPANSIONPLUGIN_API UVRGripInterface: public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + + +/** Delegate for notification when the controller grips a new object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnGripSignature, UGripMotionControllerComponent *, GrippingController, const FBPActorGripInformation&, GripInformation); + +/** Delegate for notification when the controller drops a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FVROnDropSignature, UGripMotionControllerComponent*, GrippingController, const FBPActorGripInformation&, GripInformation, bool, bWasSocketed); + +class VREXPANSIONPLUGIN_API IVRGripInterface +{ + GENERATED_IINTERFACE_BODY() + +public: + + // Set up as deny instead of allow so that default allows for gripping + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface", meta = (DisplayName = "IsDenyingGrips")) + bool DenyGripping(UGripMotionControllerComponent * GripInitiator = nullptr); + + // How an interfaced object behaves when teleporting + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripInterfaceTeleportBehavior TeleportBehavior(); + + // Should this object simulate on drop + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool SimulateOnDrop(); + + // Grip type to use + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripCollisionType GetPrimaryGripType(bool bIsSlot); + + // Double Grip Type + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + ESecondaryGripType SecondaryGripType(); + + // Define the late update setting + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripLateUpdateSettings GripLateUpdateSetting(); + + // Define which movement repliation setting to use + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripMovementReplicationSettings GripMovementReplicationType(); + + // What grip stiffness and damping to use if using a physics constraint + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void GetGripStiffnessAndDamping(float &GripStiffnessOut, float &GripDampingOut); + + // Get the advanced physics settings for this grip + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + FBPAdvGripSettings AdvancedGripSettings(); + + // What distance to break a grip at (only relevent with physics enabled grips + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + float GripBreakDistance(); + + /** + * Called to get the closest grip socket in range + * @param WorldLocation - World Location to check near + * @param bSecondarySlot - True if this is a check for a secondary slot or not + * @param CallingController - Controller checking for the slot (can be used in overrides for per hand checks) + * @param OverridePrefix - A different substring to check against in the socket names to find relevant ones + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void ClosestGripSlotInRange(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController = nullptr, FName OverridePrefix = NAME_None); + + // Events that can be called for interface inheriting actors + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void TickGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime); + + // Event triggered on the interfaced object when gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when child component is gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnChildGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when child component is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnChildGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when secondary gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGrip(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when secondary grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGripRelease(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Interaction Functions + + // Call to use an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnUsed(); + + // Call to stop using an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnEndUsed(); + + // Call to use an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnSecondaryUsed(); + + // Call to stop using an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnEndSecondaryUsed(); + + // Call to send an action event to the object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnInput(FKey Key, EInputEvent KeyEvent); + + // Check if an object allows multiple grips at one time + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool AllowsMultipleGrips(); + + // Returns if the object is held and if so, which controllers are holding it + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void IsHeld(TArray & HoldingControllers, bool & bIsHeld); + + // Sets is held, used by the plugin + UFUNCTION(BlueprintNativeEvent, /*BlueprintCallable,*/ Category = "VRGripInterface") + void SetHeld(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld); + + // Returns if the object requests to be socketed to something + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool RequestsSocketing(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform); + + // Get grip scripts + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool GetGripScripts(TArray& ArrayReference); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h new file mode 100644 index 0000000..eb8a7ab --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRRootComponent.h" +#include "VRBaseCharacterMovementComponent.h" +#include "Navigation/PathFollowingComponent.h" +#include "AbstractNavData.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRPathFollowingComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogPathFollowingVR, Warning, All); + +UCLASS() +class VREXPANSIONPLUGIN_API UVRPathFollowingComponent : public UPathFollowingComponent +{ + GENERATED_BODY() + +public: + UPROPERTY(transient) + UVRBaseCharacterMovementComponent* VRMovementComp; + + // Add link to VRMovementComp + void SetMovementComponent(UNavMovementComponent* MoveComp) override; + + ///////UPDATE WITH 4.13//////// + // Have to override this to call the correct HasReachCurrentTarget + void UpdatePathSegment() override; + bool HasReachedCurrentTarget(const FVector& CurrentLocation) const; + + // Had to override this to get the correct DebugReachTest + virtual void GetDebugStringTokens(TArray& Tokens, TArray& Flags) const override; + void DebugReachTest(float& CurrentDot, float& CurrentDistance, float& CurrentHeight, uint8& bDotFailed, uint8& bDistanceFailed, uint8& bHeightFailed) const; + + void FollowPathSegment(float DeltaTime) override; + int32 DetermineStartingPathPoint(const FNavigationPath* ConsideredPath) const override; + + + // This has a foot location when using meta paths, i'm not overriding it yet but this means that meta paths might have slightly bugged implementation. + /** notify about finished movement */ + //virtual void OnPathFinished(const FPathFollowingResult& Result) override; + + /** pause path following + * @param RequestID - request to pause, FAIRequestID::CurrentRequest means pause current request, regardless of its ID */ + void PauseMove(FAIRequestID RequestID = FAIRequestID::CurrentRequest, EPathFollowingVelocityMode VelocityMode = EPathFollowingVelocityMode::Reset) override; + // Now has an actor feet call .......Just a debug reference + //virtual FAIRequestID RequestMove(FNavPathSharedPtr Path, FRequestCompletedSignature OnComplete, const AActor* DestinationActor = NULL, float AcceptanceRadius = UPathFollowingComponent::DefaultAcceptanceRadius, bool bStopOnOverlap = true, FCustomMoveSharedPtr GameData = NULL) override; + // + // Fine in 4.13 + bool ShouldCheckPathOnResume() const override; + + // Fine in 4.13, had to change feet based for both + bool UpdateBlockDetection() override; + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h new file mode 100644 index 0000000..aa70f8a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h @@ -0,0 +1,86 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "Engine/LocalPlayer.h" +#include "VRPlayerController.generated.h" + +// A base player controller specifically for handling OnCameraManagerCreated. +// Used in case you don't want the VRPlayerCharacter changes in a PendingPlayerController +UCLASS() +class VREXPANSIONPLUGIN_API AVRBasePlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + + // Event called in BPs when the camera manager is created (only fired on locally controlled player controllers) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnCameraManagerCreated"), Category = Actor) + void OnCameraManagerCreated(APlayerCameraManager* CameraManager); + + virtual void SpawnPlayerCameraManager() override + { + Super::SpawnPlayerCameraManager(); + + if (PlayerCameraManager != NULL && IsLocalController()) + { + OnCameraManagerCreated(PlayerCameraManager); + } + } + +}; + + +UCLASS() +class VREXPANSIONPLUGIN_API AVRPlayerController : public AVRBasePlayerController +{ + GENERATED_BODY() + +public: + AVRPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // New path finding return, not actually sending anything currently unless the character created one for us + // or the user added one to us. The default implementation is fine for us. + //virtual IPathFollowingAgentInterface* GetPathFollowingAgent() const override; + + // Disable the ServerUpdateCamera function defaulted on in PlayerCameraManager + // We are manually replicating the camera position and rotation ourselves anyway + // Generally that function will just be additional replication overhead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRPlayerController") + bool bDisableServerUpdateCamera; + + /** spawn cameras for servers and owning players */ + virtual void SpawnPlayerCameraManager() override; + + FRotator LastRotationInput; + + /** + * Processes player input (immediately after PlayerInput gets ticked) and calls UpdateRotation(). + * PlayerTick is only called if the PlayerController has a PlayerInput object. Therefore, it will only be called for locally controlled PlayerControllers. + * I am overriding this so that for VRCharacters it doesn't apply the view rotation and instead lets CMC handle it + */ + virtual void PlayerTick(float DeltaTime) override; +}; + +/** +* Utility class, when set as the default local player it will spawn the target PlayerController class instead as the pending player controller +*/ +UCLASS(Blueprintable, meta = (ShortTooltip = "Utility class, when set as the default local player it will spawn the target PlayerController class instead as the pending one")) +class VREXPANSIONPLUGIN_API UVRLocalPlayer : public ULocalPlayer +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "LocalPlayer") + TSubclassOf OverridePendingLevelPlayerControllerClass; + + virtual bool SpawnPlayActor(const FString& URL, FString& OutError, UWorld* InWorld) + { + if (OverridePendingLevelPlayerControllerClass) + { + PendingLevelPlayerControllerClass = OverridePendingLevelPlayerControllerClass; + } + + return Super::SpawnPlayActor(URL, OutError, InWorld); + } +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h new file mode 100644 index 0000000..7c81303 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h @@ -0,0 +1,296 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "Components/ShapeComponent.h" +#include "VRTrackedParentInterface.h" +#include "VRBaseCharacter.h" +#include "VRExpansionFunctionLibrary.h" +#include "GameFramework/PhysicsVolume.h" +#include "Components/CapsuleComponent.h" +#include "VRRootComponent.generated.h" + +//For UE4 Profiler ~ Stat Group +//DECLARE_STATS_GROUP(TEXT("VRPhysicsUpdate"), STATGROUP_VRPhysics, STATCAT_Advanced); + +//class AVRBaseCharacter; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRRootComponent, Log, All); + +DECLARE_STATS_GROUP(TEXT("VRRootComponent"), STATGROUP_VRRootComponent, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("VR Root Set Half Height"), STAT_VRRootSetHalfHeight, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("VR Root Set Capsule Size"), STAT_VRRootSetCapsuleSize, STATGROUP_VRRootComponent); + +/** +* A capsule component that repositions its physics scene and rendering location to the camera/HMD's relative position. +* Generally not to be used by itself unless on a base Pawn and not a character, the VRCharacter has been highly customized to correctly support it. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UVRRootComponent : public UCapsuleComponent, public IVRTrackedParentInterface +{ + GENERATED_BODY() + +public: + UVRRootComponent(const FObjectInitializer& ObjectInitializer); + + friend class FDrawCylinderSceneProxy; + + bool bCalledUpdateTransform; + + // Overriding this and applying the offset to world position for the elements + virtual void GetNavigationData(FNavigationRelevantData& Data) const override; + + FORCEINLINE void GenerateOffsetToWorld(bool bUpdateBounds = true, bool bGetPureYaw = true); + + // If valid will use this as the tracked parent instead of the HMD / Parent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRTrackedParentInterface") + FBPVRWaistTracking_Info OptionalWaistTrackingParent; + + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) override + { + IVRTrackedParentInterface::Default_SetTrackedParent_Impl(NewParentComponent, WaistRadius, WaistTrackingMode, OptionalWaistTrackingParent, this); + } + + // Get the target height offset for the current capsule settings + // Generally for when using bRetainRoomscale + inline FVector GetTargetHeightOffset() + { + //return FVector::ZeroVector; + return FVector(0.f, 0.f, (-this->GetUnscaledCapsuleHalfHeight()) - VRCapsuleOffset.Z); + } + + /** + * This is overidden for the VR Character to re-set physics location + * If updating the capsule height in VR you should be using this function or SetCapsuleHalfHeightVR + * Change the capsule size. This is the unscaled size, before component scale is applied. + * @param InRadius : radius of end-cap hemispheres and center cylinder. + * @param InHalfHeight : half-height, from capsule center to end of top or bottom hemisphere. + * @param bUpdateOverlaps: if true and this shape is registered and collides, updates touching array for owner actor. + */ + UFUNCTION(BlueprintCallable, Category = "Components|Capsule") + virtual void SetCapsuleSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true); + + // Used to update the capsule half height and calculate the new offset value for VR + // If updating the capsule height in VR you should be using this function or SetCapsuleSizeVR + UFUNCTION(BlueprintCallable, Category = "Components|Capsule") + void SetCapsuleHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true); + + inline void OnUpdateTransform_Public(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) + { + OnUpdateTransform(UpdateTransformFlags, Teleport); + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + + virtual void SetSimulatePhysics(bool bSimulate) override; + +protected: + virtual bool MoveComponentImpl(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit = NULL, EMoveComponentFlags MoveFlags = MOVECOMP_NoFlags, ETeleportType Teleport = ETeleportType::None) override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) override; + + void SendPhysicsTransform(ETeleportType Teleport); + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps = nullptr, bool bDoNotifies = true, const TOverlapArrayView* OverlapsAtEndLocation = nullptr) override; + + ///** Convert a set of overlaps from a symmetric change in rotation to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ + template + bool ConvertRotationOverlapsToCurrentOverlapsVR(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps); + + template + bool GetOverlapsWithActor_TemplateVR(const AActor* Actor, TArray& OutOverlaps) const; + + /** Convert a set of overlaps from a sweep to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ + template + bool ConvertSweptOverlapsToCurrentOverlapsVR(TArray& OutOverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, const FVector& EndLocation, const FQuat& EndRotationQuat); + + +public: + void BeginPlay() override; + virtual void InitializeComponent() override; + + bool IsLocallyControlled() const; + + UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + TObjectPtr TargetPrimitiveComponent; + + //UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + TObjectPtr owningVRChar; + + //UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + //UCapsuleComponent * VRCameraCollider; + + FVector DifferenceFromLastFrame; + //UPROPERTY(BlueprintReadOnly, Transient, Category = "VRExpansionLibrary") + FTransform OffsetComponentToWorld; + + // Used to offset the collision (IE backwards from the player slightly. + // The default 2.15 Z offset is to account for floor hover from the character movement component. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + FVector VRCapsuleOffset; + + // Store last half height so that we can offset the net smoother based on changes to it + float LastCapsuleHalfHeight = 0.0f; + + // Sample current and last capsule half heights and apply an offset based on the difference + void UpdateCharacterCapsuleOffset(); + + + + // If true we will stop tracking the camera / hmd until enabled again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bPauseTracking; + + UFUNCTION(BlueprintCallable, Category = "VRExpansionLibrary") + void SetTrackingPaused(bool bPaused); + + // #TODO: Test with 100.f rounding to make sure it isn't noticable, currently that is what it is + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + /*UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bOffsetByHMD; + */ + + + + // #TODO: See if making this multiplayer compatible is viable + // Offsets capsule to be centered on HMD - currently NOT multiplayer compatible + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bCenterCapsuleOnHMD; + + // Allows the root component to be blocked by simulating objects (default off due to sickness inducing stuttering). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bAllowSimulatingCollision; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUseWalkingCollisionOverride; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + TEnumAsByte WalkingCollisionOverride; + + bool bIsOverridingCollision = false; + TEnumAsByte OriginalCollision = ECollisionChannel::ECC_Pawn; + + void SetCollisionOverride(bool bOverrideCollision) + { + if (bOverrideCollision && !bIsOverridingCollision) + { + OriginalCollision = this->GetCollisionObjectType(); + SetCollisionObjectType(WalkingCollisionOverride); + bIsOverridingCollision = true; + } + else if (!bOverrideCollision && bIsOverridingCollision) + { + SetCollisionObjectType(OriginalCollision); + bIsOverridingCollision = false; + } + } + + FVector curCameraLoc; + FRotator curCameraRot; + FRotator StoredCameraRotOffset; + + FVector lastCameraLoc = FVector::ZeroVector; + FRotator lastCameraRot = FRotator::ZeroRotator; + bool bTickedOnce = false; + + // While misnamed, is true if we collided with a wall/obstacle due to the HMDs movement in this frame (not movement components) + UPROPERTY(BlueprintReadOnly, Category = "VRExpansionLibrary") + bool bHadRelativeMovement; + + FPrimitiveSceneProxy* CreateSceneProxy() override; + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void UpdatePhysicsVolume(bool bTriggerNotifiers) override; + + inline bool AreWeOverlappingVolume(APhysicsVolume* V) + { + bool bInsideVolume = true; + if (!V->bPhysicsOnContact) + { + FVector ClosestPoint(0.f); + // If there is no primitive component as root we consider you inside the volume. This is odd, but the behavior + // has existed for a long time, so keeping it this way + UPrimitiveComponent* RootPrimitive = Cast(V->GetRootComponent()); + if (RootPrimitive) + { + float DistToCollisionSqr = -1.f; + if (RootPrimitive->GetSquaredDistanceToCollision(OffsetComponentToWorld.GetTranslation(), DistToCollisionSqr, ClosestPoint)) + { + bInsideVolume = (DistToCollisionSqr == 0.f); + } + else + { + bInsideVolume = false; + } + } + } + + return bInsideVolume; + } + +public: + // Begin UObject interface +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + void PreEditChange(FProperty* PropertyThatWillChange); +#endif // WITH_EDITOR + // End UObject interface + + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + + private: + friend class FVRCharacterScopedMovementUpdate; +}; + + +// Have to declare inlines here for blueprint +void inline UVRRootComponent::GenerateOffsetToWorld(bool bUpdateBounds, bool bGetPureYaw) +{ + FRotator CamRotOffset; + + if (bGetPureYaw) + CamRotOffset = StoredCameraRotOffset;//UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + else + CamRotOffset = curCameraRot; + + /*if(bOffsetByHMD) + { + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), FVector(0, 0, bCenterCapsuleOnHMD ? curCameraLoc.Z : CapsuleHalfHeight) + CamRotOffset.RotateVector(VRCapsuleOffset), FVector(1.0f)) * GetComponentTransform(); + } + else*/ + + if(owningVRChar && !owningVRChar->bRetainRoomscale) + { + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), FVector(0.0f, 0.0f, bCenterCapsuleOnHMD ? curCameraLoc.Z : 0.0f), FVector(1.0f)) * GetComponentTransform(); + } + else + { + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), FVector(curCameraLoc.X, curCameraLoc.Y, bCenterCapsuleOnHMD ? curCameraLoc.Z : CapsuleHalfHeight) + CamRotOffset.RotateVector(VRCapsuleOffset), FVector(1.0f)) * GetComponentTransform(); + } + + if (owningVRChar) + { + owningVRChar->OffsetComponentToWorld = OffsetComponentToWorld; + + // Check if we need to move our parents NetSmoother into place + UpdateCharacterCapsuleOffset(); + } + + if (bUpdateBounds) + UpdateBounds(); +} + + +FORCEINLINE void UVRRootComponent::SetCapsuleHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + SCOPE_CYCLE_COUNTER(STAT_VRRootSetHalfHeight); + + if (FMath::IsNearlyEqual(HalfHeight, CapsuleHalfHeight)) + { + return; + } + + SetCapsuleSizeVR(GetUnscaledCapsuleRadius(), HalfHeight, bUpdateOverlaps); +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h new file mode 100644 index 0000000..92f871f --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h @@ -0,0 +1,242 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "VRBPDatatypes.h" +#include "Components/StereoLayerComponent.h" +#include "Components/WidgetComponent.h" +//#include "Animation/UMGSequencePlayer.h" + +#include "VRStereoWidgetComponent.generated.h" + +class FWidgetRenderer; +class UUserWidget; +class UTextureRenderTarget2D; +class UStereoLayerShape; + + +/** +* A stereo component that displays a widget instead of a texture. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin), HideCategories = ("Stereoscopic Properties", Collision)) +class VREXPANSIONPLUGIN_API UVRStereoWidgetRenderComponent : public UStereoLayerComponent +{ + GENERATED_BODY() + +public: + UVRStereoWidgetRenderComponent(const FObjectInitializer& ObjectInitializer); + + /** The class of User Widget to create and display an instance of */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + TSubclassOf WidgetClass; + + /** The User Widget object displayed and managed by this component */ + UPROPERTY(BlueprintReadWrite, Transient, DuplicateTransient, Category = "WidgetSettings") + TObjectPtr Widget; + + /** If true then we sample the requested size of the widget and reset the texture to be that size */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bDrawAtDesiredSize; + + /** The desired render scale of the widget */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float WidgetRenderScale; + + /** The desired render gamma of the widget */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float WidgetRenderGamma; + + /** Automatically correct for gamma */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bUseGammaCorrection; + + /** The desired clear color of the render target */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + FLinearColor RenderTargetClearColor; + + /** If true we will draw to the render target even without active stereo layers and skip the stereo tick*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bDrawWithoutStereo; + + /** Rate (HTZ) we should draw the texture at */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float DrawRate; + + // Counts how long until next draw + float DrawCounter; + + /** The Slate widget to be displayed by this component. Only one of either Widget or SlateWidget can be used */ + TSharedPtr SlateWidget; + + class FWidgetRenderer* WidgetRenderer; + + /** The render target being display */ + UPROPERTY(BlueprintReadOnly, Transient, DuplicateTransient, Category = "WidgetSettings") + TObjectPtr RenderTarget; + + /** The slate window that contains the user widget content */ + TSharedPtr SlateWindow; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void DestroyComponent(bool bPromoteChildren/*= false*/) override; + + UFUNCTION(BlueprintCallable, Category = "WidgetSettings") + void SetWidgetAndInit(TSubclassOf NewWidgetClass); + + void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld); + void InitWidget(); + void RenderWidget(float DeltaTime); + void ReleaseResources(); +}; + + +/** +* A widget component that displays the widget in a stereo layer instead of in worldspace. +* Currently this class uses a custom postion instead of the engines WorldLocked for stereo layers +* This is because world locked stereo layers don't account for player movement currently. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRStereoWidgetComponent : public UWidgetComponent +{ + GENERATED_BODY() + +public: + UVRStereoWidgetComponent(const FObjectInitializer& ObjectInitializer); + + friend class FStereoLayerComponentVisualizer; + + ~UVRStereoWidgetComponent(); + + + /** Specifies which shape of layer it is. Note that some shapes will be supported only on certain platforms! **/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, NoClear, Instanced, Category = "StereoLayer", DisplayName = "Stereo Layer Shape") + TObjectPtr Shape; + + void BeginDestroy() override; + void OnUnregister() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void DrawWidgetToRenderTarget(float DeltaTime) override; + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyVRComponentInstanceData(class FVRStereoWidgetComponentInstanceData* WidgetInstanceData); + + virtual void UpdateRenderTarget(FIntPoint DesiredRenderTargetSize) override; + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + + + /** If true then this stereo widget will skip visibility checks when in stereo mode */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bAlwaysVisible = false; + + // If true forces the widget to render both stereo and world widgets + // Overriden by the console command vr.ForceNoStereoWithVRWidgets if it is set to 1 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bRenderBothStereoAndWorld; + + /** Forces the widget to skip stereo regardless of all other settings (localized version of vr.ForceNoStereoWithVRWidgets)*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bDrawWithoutStereo; + + // If true, use Epics world locked stereo implementation instead of my own temp solution + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bUseEpicsWorldLockedStereo; + + // If true, will cache and delay the transform adjustment for one frame in order to sync with the game thread better + // Not for use with late updated parents. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bDelayForRenderThread; + + // If true will not render or update until false + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bIsSleeping; + + /** + * Change the layer's render priority, higher priorities render on top of lower priorities + * @param InPriority: Priority value + */ + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetPriority(int32 InPriority); + + // @return the render priority + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + int32 GetPriority() const { return Priority; } + + /** True if the stereo layer needs to support depth intersections with the scene geometry, if available on the platform */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bSupportsDepth : 1; + + /** True if the texture should not use its own alpha channel (1.0 will be substituted) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bNoAlphaChannel : 1; + + /** True if the quad should internally set it's Y value based on the set texture's dimensions */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bQuadPreserveTextureRatio : 1; + +protected: + /** Texture displayed on the stereo layer (is stereocopic textures are supported on the platfrom and more than one texture is provided, this will be the right eye) **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "StereoLayer") + // class UTexture* Texture; + + // Forget left texture implementation + /** Texture displayed on the stereo layer for left eye, if stereoscopic textures are supported on the platform **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "StereoLayer | Cubemap Overlay Properties") + // class UTexture* LeftTexture; + +public: + /** Size of the rendered stereo layer quad **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Quad Overlay Properties") + // FVector2D StereoLayerQuadSize; + + /** UV coordinates mapped to the quad face **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Quad Overlay Properties") + FBox2D UVRect; + + /** Radial size of the rendered stereo layer cylinder **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + // float CylinderRadius; + + /** Arc angle for the stereo layer cylinder **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + //float CylinderOverlayArc; + + /** Height of the stereo layer cylinder **/ + ///UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + // int CylinderHeight; + + /** Specifies how and where the quad is rendered to the screen **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + //TEnumAsByte StereoLayerType; + + // Forcing quad layer so that it works with the widget better + /** Specifies which type of layer it is. Note that some shapes will be supported only on certain platforms! **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + // TEnumAsByte StereoLayerShape; + + /** Render priority among all stereo layers, higher priority render on top of lower priority **/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + int32 Priority; + + bool bShouldCreateProxy; + +private: + /** Dirty state determines whether the stereo layer needs updating **/ + bool bIsDirty; + bool bDirtyRenderTarget; + + /** Texture needs to be marked for update **/ + bool bTextureNeedsUpdate; + + /** IStereoLayer id, 0 is unassigned **/ + uint32 LayerId; + + /** Last transform is cached to determine if the new frames transform has changed **/ + FTransform LastTransform; + + /** Last frames visiblity state **/ + bool bLastVisible; + +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h new file mode 100644 index 0000000..ae9dbd2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h @@ -0,0 +1,32 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "UObject/Interface.h" + +#include "VRTrackedParentInterface.generated.h" + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UVRTrackedParentInterface: public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + + +class VREXPANSIONPLUGIN_API IVRTrackedParentInterface +{ + GENERATED_IINTERFACE_BODY() + +public: + + // Set a tracked parent + UFUNCTION(BlueprintCallable, Category = "VRTrackedParentInterface") + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) + {} + + static void Default_SetTrackedParent_Impl(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode, FBPVRWaistTracking_Info & OptionalWaistTrackingParent, USceneComponent * Self); + + // Returns local transform of the parent relative attachment + static FTransform Default_GetWaistOrientationAndPosition(FBPVRWaistTracking_Info & WaistTrackingInfo); +}; \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs new file mode 100644 index 0000000..23c324d --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs @@ -0,0 +1,141 @@ +// Some copyright should be here... +using System.IO; +using UnrealBuildTool; + +public class VRExpansionPlugin : ModuleRules +{ + private string PluginsPath + { + get { return Path.GetFullPath(Target.RelativeEnginePath) + "Plugins/Runtime/"; } + } + + public VRExpansionPlugin(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //bEnforceIWYU = true; + + PublicDefinitions.Add("WITH_VR_EXPANSION=1"); + + // To detect VR Preview, not built out in packaged builds + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "UnrealEd" + } + ); + } + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "Settings" + } + ); + + PublicIncludePaths.AddRange( + new string[] { + //"VRExpansionPlugin/Public", + //"VRExpansionPlugin/Public/SimpleChar", + //"HeadMountedDisplay/Public", + //"Runtime/Engine/Private/PhysicsEngine" + + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + //"VRExpansionPlugin/Private", + //"VRExpansionPlugin/Private/SimpleChar", + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "NetCore", + "CoreUObject", + "Engine", + // "InputCore", + "PhysicsCore", + //"FLEX", remove comment if building in the NVIDIA flex branch - NOTE when put in place FLEX only listed win32 and win64 at compatible platforms + "HeadMountedDisplay", + // "RHI", + //"RenderCore", + //"ShaderCore", + //"NetworkReplayStreaming", + //"AIModule", + "UMG", + "NavigationSystem", + "AIModule", + "AnimGraphRuntime", + "XRBase", + "GameplayTags" + //"Renderer", + //"UtilityShaders" + }); + + //if(Target.bUseChaos) + // { + PublicDependencyModuleNames.Add("Chaos"); + PublicDependencyModuleNames.Add("ChaosVehicles"); + //} + + PrivateDependencyModuleNames.AddRange( + new string[] + { + // "Core", + // "CoreUObject", + //"Engine", + "InputCore", + //"FLEX", remove comment if building in the NVIDIA flex branch - NOTE when put in place FLEX only listed win32 and win64 at compatible platforms + //"HeadMountedDisplay", + "RHI", + "ApplicationCore", + "RenderCore", + // "ShaderCore", + "NetworkReplayStreaming" + //"Renderer", + // "UtilityShaders" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + //"CoreUObject", + //"Engine", + "Slate", + "SlateCore" + + // ... add private dependencies that you statically link with here ... + } + ); + + // Don't load APEX on incompatible platforms + /* if ( + Target.Platform != UnrealTargetPlatform.IOS && + Target.Platform != UnrealTargetPlatform.TVOS && + Target.Platform != UnrealTargetPlatform.Android && + Target.Platform != UnrealTargetPlatform.HTML5) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "APEX" + }); + }*/ + + // Allow gameplay debugger on editor builds + if (Target.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test)) + { + PrivateDependencyModuleNames.Add("GameplayDebugger"); + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1"); // Already in AI Module, but gameplay abilities and other modules also duplicate the definition + } + else + { + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0"); + } + } +} diff --git a/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin new file mode 100644 index 0000000..340ea98 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin @@ -0,0 +1,44 @@ +{ + "FileVersion": 3, + "Version": 5.3, + "VersionName": "5.3", + "FriendlyName": "VRExpansionPlugin", + "Description": "Adds several new VR features & components to UE4", + "Category": "VRExpansion", + "CreatedBy": "Joshua (MordenTral) Statzer", + "CreatedByURL": "http://www.vreue4.com", + "DocsURL": "http://www.vreue4.com", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ], + "Modules": [ + { + "Name": "VRExpansionPlugin", + "Type": "RunTime", + "LoadingPhase": "Default" + }, + { + "Name": "VRExpansionEditor", + "Type": "UnCookedOnly", + "LoadingPhase": "PostEngineInit" + } + ], + "Plugins": [ + { + "Name": "ChaosVehiclesPlugin", + "Enabled": true + }, + { + "Name": "XRBase", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/README.md b/VIRTUOS_ExpansionPluginTests/README.md new file mode 100644 index 0000000..02dcdcf --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/README.md @@ -0,0 +1,10 @@ +### How do I get set up? ### + +Current compatible versions for template: Latest Engine Version (Template is not kept to as many compatible versions as the plugin itself). + +* Right click on VRExpPluginExample.uproject and switch to your preferred (compatible) engine version. +* If project files did not automatically generate after switching, right click again and select "Generate Visual Studio Files" +* Open Solution and build - Or download the pre-built binary package from the forum thread for the engine version and place into the plugins directory. +* Run + +You need to have visual studio installed and follow the UE4 setup guide for it: https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/ \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample.Target.cs b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample.Target.cs new file mode 100644 index 0000000..3ecbc98 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample.Target.cs @@ -0,0 +1,61 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class VRExpPluginExampleTarget : TargetRules +{ + public VRExpPluginExampleTarget(TargetInfo Target) : base(Target) + { + + DefaultBuildSettings = BuildSettingsVersion.V2; + IncludeOrderVersion = EngineIncludeOrderVersion.Latest; + + //bUseLoggingInShipping = true; + Type = TargetType.Game; + ExtraModuleNames.AddRange(new string[] { "VRExpPluginExample" }); + //bUsePCHFiles = false; + //bUseUnityBuild = false; + + /* + * This is our Steam App ID. + * # Define in both server and client targets + */ + ProjectDefinitions.Add("UE_PROJECT_STEAMSHIPPINGID=480"); + + + + /* + * This is used on SetProduct(), and should be the same as your Product Name + * under Dedicated Game Server Information in Steamworks + * # Define in the Server target + */ + //ProjectDefinitions.Add("UE_PROJECT_STEAMPRODUCTNAME=\"MyGame\""); + + /* + * This is used on SetModDir(), and should be the same as your Product Name + * under Dedicated Game Server Information in Steamworks + * # Define in the client target + */ + //ProjectDefinitions.Add("UE_PROJECT_STEAMGAMEDIR=\"MyGame\""); + + /* + * This is what shows up under the game filter in Steam server browsers. + * # Define in both server and client targets + */ + //ProjectDefinitions.Add("UE_PROJECT_STEAMGAMEDESC=\"My Game\""); + } + + // + // TargetRules interface. + // + + /*public override void SetupBinaries( + TargetInfo Target, + ref List OutBuildBinaryConfigurations, + ref List OutExtraModuleNames + ) + { + OutExtraModuleNames.AddRange( new string[] { "VRExpPluginExample" } ); + }*/ +} diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.cpp b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.cpp new file mode 100644 index 0000000..5cd3af7 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.cpp @@ -0,0 +1,12 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +//#include "VRExpPluginExample.h" +#include "EmptyClassForProject.h" + +EmptyClassForProject::EmptyClassForProject() +{ +} + +EmptyClassForProject::~EmptyClassForProject() +{ +} diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.h b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.h new file mode 100644 index 0000000..bc90159 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/EmptyClassForProject.h @@ -0,0 +1,27 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "VRCharacter.h" +#include "GripMotionControllerComponent.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "VRPlayerController.h" +#include "VRExpansionFunctionLibrary.h" +#include "Grippables/GrippableBoxComponent.h" +#include "VRStereoWidgetComponent.h" +#include "VRCharacterMovementComponent.h" +#include "Misc/VRLogComponent.h" +#include "VRAIController.h" +#include "VRPathFollowingComponent.h" +#include "VRRootComponent.h" + +/** + * + */ +class VREXPPLUGINEXAMPLE_API EmptyClassForProject +{ +public: + EmptyClassForProject(); + ~EmptyClassForProject(); + +}; diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.Build.cs b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.Build.cs new file mode 100644 index 0000000..deec2f2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.Build.cs @@ -0,0 +1,24 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class VRExpPluginExample : ModuleRules +{ + public VRExpPluginExample(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + // PrivatePCHHeaderFile = "Private/WindowsMixedRealityPrecompiled.h"; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AdvancedSessions", "VRExpansionPlugin" }); + + 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/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.cpp b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.cpp new file mode 100644 index 0000000..959caaa --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.cpp @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRExpPluginExample.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, VRExpPluginExample, "VRExpPluginExample" ); diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.h b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.h new file mode 100644 index 0000000..3d85d22 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExample/VRExpPluginExample.h @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "Engine.h" + diff --git a/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExampleEditor.Target.cs b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExampleEditor.Target.cs new file mode 100644 index 0000000..b31ef8c --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/Source/VRExpPluginExampleEditor.Target.cs @@ -0,0 +1,31 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class VRExpPluginExampleEditorTarget : TargetRules +{ + public VRExpPluginExampleEditorTarget(TargetInfo Target) : base(Target) + { + DefaultBuildSettings = BuildSettingsVersion.V2; + IncludeOrderVersion = EngineIncludeOrderVersion.Latest; + + Type = TargetType.Editor; + ExtraModuleNames.AddRange(new string[] { "VRExpPluginExample" }); + //bUseUnityBuild = false; + //bUsePCHFiles = false; + } + + // + // TargetRules interface. + // + +/* public override void SetupBinaries( + TargetInfo Target, + ref List OutBuildBinaryConfigurations, + ref List OutExtraModuleNames + ) + { + OutExtraModuleNames.AddRange( new string[] { "VRExpPluginExample" } ); + }*/ +} diff --git a/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/App.config b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Program.cs b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Program.cs new file mode 100644 index 0000000..1ad7b1a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Program.cs @@ -0,0 +1,104 @@ +/* + * + * This is just a really quick mockup of a program to rename base path UE4 projects, nothing fancy and nothing all that well executed, I am very c# rusty. + * + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; +namespace UE4ProjectRenamer +{ + class Program + { + static void Main(string[] args) + { + string FilePath = Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetEntryAssembly().CodeBase).LocalPath); + string [] FoundFiles = Directory.GetFiles(FilePath, "*.uproject", SearchOption.TopDirectoryOnly); + + if (FoundFiles.Length < 1) + { + MessageBox.Show("Uproject not found in .exe directory, please move this .exe to the uproject directory prior to running"); + return; + } + + string TargetProject = FoundFiles[0]; + + if(MessageBox.Show("Found uproject by name of: " + Path.GetFileName(TargetProject) + " - Do you want to rename this project?", "Rename this project?", MessageBoxButtons.OKCancel) == DialogResult.Cancel) + { + return; + } + + string OldProjectName = Path.GetFileNameWithoutExtension(TargetProject); + string NewProjectName = Microsoft.VisualBasic.Interaction.InputBox("Enter the new project name for: " + OldProjectName, "EnterNewName", "Default", -1, -1); + + if (NewProjectName == "Default" || NewProjectName.Length < 1) + return; + + if (MessageBox.Show("Preparing to rename: " + OldProjectName + " to " + NewProjectName + ", Are you sure?", "Rename this project?", MessageBoxButtons.OKCancel) == DialogResult.Cancel) + { + return; + } + + // Remove old solution files + File.Delete(FilePath + "\\" + OldProjectName + ".sln"); + File.Delete(FilePath + "\\" + OldProjectName + ".sdf"); + File.Delete(FilePath + "\\" + OldProjectName + ".VC.db"); + + // Replace the .uproject + OpenAndRenameFile(TargetProject, OldProjectName, NewProjectName); + + + List FileList = new List(); + GetFilesInDirectory(FilePath + "\\Source", ref FileList); + + // Replace all interior source and .cs files + foreach(string file in FileList) + { + OpenAndRenameFile(file, OldProjectName, NewProjectName); + } + + // Rename source folder + Directory.Move(FilePath + "\\Source\\" + OldProjectName, FilePath + "\\Source\\" + NewProjectName); + + MessageBox.Show("Finished renaming all files in source directory, you will need to re-generate your project files now, rename the project folder, and change the name in defaultengine.ini"); + } + + static bool OpenAndRenameFile(string FileName, string OldProjectName, string NewProjectName) + { + try + { + string text = File.ReadAllText(FileName); + text = text.Replace(OldProjectName.ToUpper() + "_API", NewProjectName.ToUpper() + "_API"); + text = text.Replace(OldProjectName, NewProjectName); + + string newFileName = Path.GetDirectoryName(FileName) + "\\" + Path.GetFileName(FileName).Replace(OldProjectName, NewProjectName); + File.WriteAllText(newFileName, text); + + if (FileName != newFileName && File.Exists(FileName)) + { + File.Delete(FileName); + } + } + catch(Exception exp) + { + MessageBox.Show("Failed to open and modify " + Path.GetFileName(FileName) + "! " + exp.Message); + return false; + } + + return true; + } + + static bool GetFilesInDirectory(string nDirectory, ref List StringList) + { + string[] FoundFiles = Directory.GetFiles(nDirectory, "*", SearchOption.AllDirectories); + + StringList.AddRange(FoundFiles); + return true; + } + } +} diff --git a/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Properties/AssemblyInfo.cs b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..98dd11a --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UE4ProjectRenamer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UE4ProjectRenamer")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f6f1963a-7658-45d2-baa2-c545902c8f61")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/UE4ProjectRenamer.csproj b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/UE4ProjectRenamer.csproj new file mode 100644 index 0000000..ea885de --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/UE4ProjectRenamer/UE4ProjectRenamer/UE4ProjectRenamer.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {F6F1963A-7658-45D2-BAA2-C545902C8F61} + Exe + UE4ProjectRenamer + UE4ProjectRenamer + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VIRTUOS_ExpansionPluginTests/VRExpPluginExample.uproject b/VIRTUOS_ExpansionPluginTests/VRExpPluginExample.uproject new file mode 100644 index 0000000..35055f2 --- /dev/null +++ b/VIRTUOS_ExpansionPluginTests/VRExpPluginExample.uproject @@ -0,0 +1,71 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.3", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "VRExpPluginExample", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "OpenXR", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ] + }, + { + "Name": "VRExpansionPlugin", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ] + }, + { + "Name": "OnlineSubsystemSteam", + "Enabled": true + }, + { + "Name": "OpenXRExpansionPlugin", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ] + }, + { + "Name": "SteamVR", + "Enabled": false, + "SupportedTargetPlatforms": [ + "Win32", + "Win64", + "Linux" + ] + }, + { + "Name": "OpenXRHandTracking", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android" + ] + } + ], + "TargetPlatforms": [ + "PS4", + "WindowsNoEditor", + "Android", + "Windows", + "Linux" + ] +} \ No newline at end of file