You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
-Thread safety of Modular Features has been improved and we were hitting ensures in the render thread update of motion controllers. -Now we are caching the IMotionController that is used for the game thread update and only attempting to use that one for the render thread update. We are also watching for unregistered modules to null out cached IMotionControllers as necessary (this is unlikely, but if it happened it would be very bad). #review-19473653 #rb Robert.Srinivasiah #preflight 6255a4af3f5641db59f763ae [CL 19723508 by Jeff Fisher in ue5-main branch]
745 lines
23 KiB
C++
745 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
//
|
|
#include "MotionControllerComponent.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "PrimitiveSceneProxy.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "IMotionController.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 "XRMotionControllerBase.h"
|
|
#include "IXRTrackingSystem.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogMotionControllerComponent, Log, All);
|
|
|
|
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;
|
|
|
|
/** Console variable for specifying whether motion controller late update is used */
|
|
TAutoConsoleVariable<int32> CVarEnableMotionControllerLateUpdate(
|
|
TEXT("vr.EnableMotionControllerLateUpdate"),
|
|
1,
|
|
TEXT("This command allows you to specify whether the motion controller late update is applied.\n")
|
|
TEXT(" 0: don't use late update\n")
|
|
TEXT(" 1: use late update (default)"),
|
|
ECVF_Cheat);
|
|
} // anonymous namespace
|
|
|
|
FName UMotionControllerComponent::CustomModelSourceId(TEXT("Custom"));
|
|
|
|
namespace LegacyMotionSources
|
|
{
|
|
static bool GetSourceNameForHand(EControllerHand InHand, FName& OutSourceName)
|
|
{
|
|
UEnum* HandEnum = StaticEnum<EControllerHand>();
|
|
if (HandEnum)
|
|
{
|
|
FString ValueName = HandEnum->GetNameStringByValue((int64)InHand);
|
|
if (!ValueName.IsEmpty())
|
|
{
|
|
OutSourceName = *ValueName;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
//=============================================================================
|
|
UMotionControllerComponent::UMotionControllerComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, RenderThreadComponentScale(1.0f,1.0f,1.0f)
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
PrimaryComponentTick.bStartWithTickEnabled = true;
|
|
PrimaryComponentTick.TickGroup = TG_PrePhysics;
|
|
PrimaryComponentTick.bTickEvenWhenPaused = true;
|
|
|
|
PlayerIndex = 0;
|
|
MotionSource = FXRMotionControllerBase::LeftHandSourceId;
|
|
bDisableLowLatencyUpdate = false;
|
|
bHasAuthority = false;
|
|
bAutoActivate = true;
|
|
|
|
// ensure InitializeComponent() gets called
|
|
bWantsInitializeComponent = true;
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::BeginDestroy()
|
|
{
|
|
Super::BeginDestroy();
|
|
if (ViewExtension.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);
|
|
ViewExtension->MotionControllerComponent = NULL;
|
|
}
|
|
|
|
ViewExtension.Reset();
|
|
}
|
|
}
|
|
|
|
void UMotionControllerComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context)
|
|
{
|
|
Super::CreateRenderState_Concurrent(Context);
|
|
RenderThreadRelativeTransform = GetRelativeTransform();
|
|
RenderThreadComponentScale = GetComponentScale();
|
|
}
|
|
|
|
void UMotionControllerComponent::SendRenderTransform_Concurrent()
|
|
{
|
|
struct FPrimitiveUpdateRenderThreadRelativeTransformParams
|
|
{
|
|
FTransform RenderThreadRelativeTransform;
|
|
FVector RenderThreadComponentScale;
|
|
};
|
|
|
|
FPrimitiveUpdateRenderThreadRelativeTransformParams UpdateParams;
|
|
UpdateParams.RenderThreadRelativeTransform = GetRelativeTransform();
|
|
UpdateParams.RenderThreadComponentScale = GetComponentScale();
|
|
|
|
ENQUEUE_RENDER_COMMAND(UpdateRTRelativeTransformCommand)(
|
|
[UpdateParams, this](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
RenderThreadRelativeTransform = UpdateParams.RenderThreadRelativeTransform;
|
|
RenderThreadComponentScale = UpdateParams.RenderThreadComponentScale;
|
|
});
|
|
|
|
Super::SendRenderTransform_Concurrent();
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
|
|
{
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
|
|
if (IsActive())
|
|
{
|
|
FVector Position = GetRelativeTransform().GetTranslation();
|
|
FRotator Orientation = GetRelativeTransform().GetRotation().Rotator();
|
|
float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f;
|
|
const bool bNewTrackedState = PollControllerState(Position, Orientation, WorldToMeters);
|
|
if (bNewTrackedState)
|
|
{
|
|
SetRelativeLocationAndRotation(Position, Orientation);
|
|
}
|
|
|
|
// if controller tracking just kicked in or we haven't gotten a valid model yet
|
|
if (((!bTracked && bNewTrackedState) || !DisplayComponent) && bDisplayDeviceModel && DisplayModelSource != UMotionControllerComponent::CustomModelSourceId)
|
|
{
|
|
RefreshDisplayComponent();
|
|
}
|
|
bTracked = bNewTrackedState;
|
|
|
|
if (!ViewExtension.IsValid() && GEngine)
|
|
{
|
|
ViewExtension = FSceneViewExtensions::NewExtension<FViewExtension>(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetShowDeviceModel(const bool bShowDeviceModel)
|
|
{
|
|
if (bDisplayDeviceModel != bShowDeviceModel)
|
|
{
|
|
bDisplayDeviceModel = bShowDeviceModel;
|
|
#if WITH_EDITORONLY_DATA
|
|
const UWorld* MyWorld = GetWorld();
|
|
const bool bIsGameInst = MyWorld && MyWorld->WorldType != EWorldType::Editor && MyWorld->WorldType != EWorldType::EditorPreview;
|
|
|
|
if (!bIsGameInst)
|
|
{
|
|
// tear down and destroy the existing component if we're an editor inst
|
|
RefreshDisplayComponent(/*bForceDestroy =*/true);
|
|
}
|
|
else
|
|
#endif
|
|
if (DisplayComponent)
|
|
{
|
|
DisplayComponent->SetHiddenInGame(!bShowDeviceModel, /*bPropagateToChildren =*/false);
|
|
}
|
|
else if (!bShowDeviceModel)
|
|
{
|
|
RefreshDisplayComponent();
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetDisplayModelSource(const FName NewDisplayModelSource)
|
|
{
|
|
if (NewDisplayModelSource != DisplayModelSource)
|
|
{
|
|
DisplayModelSource = NewDisplayModelSource;
|
|
RefreshDisplayComponent();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetCustomDisplayMesh(UStaticMesh* NewDisplayMesh)
|
|
{
|
|
if (NewDisplayMesh != CustomDisplayMesh)
|
|
{
|
|
CustomDisplayMesh = NewDisplayMesh;
|
|
if (DisplayModelSource == UMotionControllerComponent::CustomModelSourceId)
|
|
{
|
|
if (UStaticMeshComponent* AsMeshComponent = Cast<UStaticMeshComponent>(DisplayComponent))
|
|
{
|
|
AsMeshComponent->SetStaticMesh(NewDisplayMesh);
|
|
}
|
|
else
|
|
{
|
|
RefreshDisplayComponent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetTrackingSource(const EControllerHand NewSource)
|
|
{
|
|
if (LegacyMotionSources::GetSourceNameForHand(NewSource, MotionSource))
|
|
{
|
|
UWorld* MyWorld = GetWorld();
|
|
if (MyWorld && MyWorld->IsGameWorld() && HasBeenInitialized())
|
|
{
|
|
FMotionDelayService::RegisterDelayTarget(this, PlayerIndex, MotionSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
EControllerHand UMotionControllerComponent::GetTrackingSource() const
|
|
{
|
|
EControllerHand Hand = EControllerHand::Left;
|
|
FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, Hand);
|
|
return Hand;
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetTrackingMotionSource(const FName NewSource)
|
|
{
|
|
MotionSource = NewSource;
|
|
|
|
UWorld* MyWorld = GetWorld();
|
|
if (MyWorld && MyWorld->IsGameWorld() && HasBeenInitialized())
|
|
{
|
|
FMotionDelayService::RegisterDelayTarget(this, PlayerIndex, NewSource);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::SetAssociatedPlayerIndex(const int32 NewPlayer)
|
|
{
|
|
PlayerIndex = NewPlayer;
|
|
|
|
UWorld* MyWorld = GetWorld();
|
|
if (MyWorld && MyWorld->IsGameWorld() && HasBeenInitialized())
|
|
{
|
|
FMotionDelayService::RegisterDelayTarget(this, NewPlayer, MotionSource);
|
|
}
|
|
}
|
|
|
|
void UMotionControllerComponent::Serialize(FArchive& Ar)
|
|
{
|
|
Ar.UsingCustomVersion(FVRObjectVersion::GUID);
|
|
|
|
Super::Serialize(Ar);
|
|
|
|
if (Ar.CustomVer(FVRObjectVersion::GUID) < FVRObjectVersion::UseFNameInsteadOfEControllerHandForMotionSource)
|
|
{
|
|
LegacyMotionSources::GetSourceNameForHand(Hand_DEPRECATED, MotionSource);
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
//=============================================================================
|
|
void UMotionControllerComponent::PreEditChange(FProperty* PropertyAboutToChange)
|
|
{
|
|
PreEditMaterialCount = DisplayMeshMaterialOverrides.Num();
|
|
Super::PreEditChange(PropertyAboutToChange);
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
FProperty* PropertyThatChanged = PropertyChangedEvent.Property;
|
|
const FName PropertyName = (PropertyThatChanged != nullptr) ? PropertyThatChanged->GetFName() : NAME_None;
|
|
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMotionControllerComponent, bDisplayDeviceModel))
|
|
{
|
|
RefreshDisplayComponent(/*bForceDestroy =*/true);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UMotionControllerComponent, DisplayMeshMaterialOverrides))
|
|
{
|
|
RefreshDisplayComponent(/*bForceDestroy =*/DisplayMeshMaterialOverrides.Num() < PreEditMaterialCount);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UMotionControllerComponent, CustomDisplayMesh))
|
|
{
|
|
RefreshDisplayComponent(/*bForceDestroy =*/false);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
if (DisplayComponent == nullptr)
|
|
{
|
|
RefreshDisplayComponent();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::InitializeComponent()
|
|
{
|
|
Super::InitializeComponent();
|
|
|
|
UWorld* MyWorld = GetWorld();
|
|
if (MyWorld && MyWorld->IsGameWorld())
|
|
{
|
|
FMotionDelayService::RegisterDelayTarget(this, PlayerIndex, MotionSource);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
|
|
{
|
|
Super::OnComponentDestroyed(bDestroyingHierarchy);
|
|
|
|
if (DisplayComponent)
|
|
{
|
|
DisplayComponent->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::RefreshDisplayComponent(const bool bForceDestroy)
|
|
{
|
|
if (IsRegistered())
|
|
{
|
|
TArray<USceneComponent*> DisplayAttachChildren;
|
|
auto DestroyDisplayComponent = [this, &DisplayAttachChildren]()
|
|
{
|
|
DisplayDeviceId.Clear();
|
|
|
|
if (DisplayComponent)
|
|
{
|
|
// @TODO: save/restore socket attachments as well
|
|
DisplayAttachChildren = DisplayComponent->GetAttachChildren();
|
|
|
|
DisplayComponent->DestroyComponent(/*bPromoteChildren =*/true);
|
|
DisplayComponent = nullptr;
|
|
}
|
|
};
|
|
if (bForceDestroy)
|
|
{
|
|
DestroyDisplayComponent();
|
|
}
|
|
|
|
UPrimitiveComponent* NewDisplayComponent = nullptr;
|
|
if (bDisplayDeviceModel)
|
|
{
|
|
const EObjectFlags SubObjFlags = RF_Transactional | RF_TextExportTransient;
|
|
|
|
if (DisplayModelSource == UMotionControllerComponent::CustomModelSourceId)
|
|
{
|
|
UStaticMeshComponent* MeshComponent = nullptr;
|
|
if ((DisplayComponent == nullptr) || (DisplayComponent->GetClass() != UStaticMeshComponent::StaticClass()))
|
|
{
|
|
DestroyDisplayComponent();
|
|
|
|
const FName SubObjName = MakeUniqueObjectName(this, UStaticMeshComponent::StaticClass(), TEXT("MotionControllerMesh"));
|
|
MeshComponent = NewObject<UStaticMeshComponent>(this, SubObjName, SubObjFlags);
|
|
}
|
|
else
|
|
{
|
|
MeshComponent = CastChecked<UStaticMeshComponent>(DisplayComponent);
|
|
}
|
|
NewDisplayComponent = MeshComponent;
|
|
|
|
if (ensure(MeshComponent))
|
|
{
|
|
if (CustomDisplayMesh)
|
|
{
|
|
MeshComponent->SetStaticMesh(CustomDisplayMesh);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMotionControllerComponent, Warning, TEXT("Failed to create a custom display component for the MotionController since no mesh was specified."));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<IXRSystemAssets*> XRAssetSystems = IModularFeatures::Get().GetModularFeatureImplementations<IXRSystemAssets>(IXRSystemAssets::GetModularFeatureName());
|
|
for (IXRSystemAssets* AssetSys : XRAssetSystems)
|
|
{
|
|
if (!DisplayModelSource.IsNone() && AssetSys->GetSystemName() != DisplayModelSource)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int32 DeviceId = INDEX_NONE;
|
|
if (MotionSource == FXRMotionControllerBase::HMDSourceId)
|
|
{
|
|
DeviceId = IXRTrackingSystem::HMDDeviceId;
|
|
}
|
|
else
|
|
{
|
|
EControllerHand ControllerHandIndex;
|
|
if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, ControllerHandIndex))
|
|
{
|
|
break;
|
|
}
|
|
DeviceId = AssetSys->GetDeviceId(ControllerHandIndex);
|
|
}
|
|
|
|
if (DisplayComponent && DisplayDeviceId.IsOwnedBy(AssetSys) && DisplayDeviceId.DeviceId == DeviceId)
|
|
{
|
|
// assume that the current DisplayComponent is the same one we'd get back, so don't recreate it
|
|
// @TODO: maybe we should add a IsCurrentlyRenderable(int32 DeviceId) to IXRSystemAssets to confirm this in some manner
|
|
break;
|
|
}
|
|
|
|
// needs to be set before CreateRenderComponent() since the LoadComplete callback may be triggered before it returns (for syncrounous loads)
|
|
DisplayModelLoadState = EModelLoadStatus::Pending;
|
|
FXRComponentLoadComplete LoadCompleteDelegate = FXRComponentLoadComplete::CreateUObject(this, &UMotionControllerComponent::OnDisplayModelLoaded);
|
|
|
|
NewDisplayComponent = AssetSys->CreateRenderComponent(DeviceId, GetOwner(), SubObjFlags, /*bForceSynchronous=*/false, LoadCompleteDelegate);
|
|
if (NewDisplayComponent != nullptr)
|
|
{
|
|
if (DisplayModelLoadState != EModelLoadStatus::Complete)
|
|
{
|
|
DisplayModelLoadState = EModelLoadStatus::InProgress;
|
|
}
|
|
DestroyDisplayComponent();
|
|
DisplayDeviceId = FXRDeviceId(AssetSys, DeviceId);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
DisplayModelLoadState = EModelLoadStatus::Unloaded;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NewDisplayComponent && NewDisplayComponent != DisplayComponent)
|
|
{
|
|
NewDisplayComponent->SetupAttachment(this);
|
|
// force disable collision - if users wish to use collision, they can setup their own sub-component
|
|
NewDisplayComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
NewDisplayComponent->RegisterComponent();
|
|
|
|
for (USceneComponent* Child : DisplayAttachChildren)
|
|
{
|
|
Child->SetupAttachment(NewDisplayComponent);
|
|
}
|
|
|
|
DisplayComponent = NewDisplayComponent;
|
|
}
|
|
|
|
if (DisplayComponent)
|
|
{
|
|
if (DisplayModelLoadState != EModelLoadStatus::InProgress)
|
|
{
|
|
OnDisplayModelLoaded(DisplayComponent);
|
|
}
|
|
|
|
DisplayComponent->SetHiddenInGame(bHiddenInGame);
|
|
DisplayComponent->SetVisibility(GetVisibleFlag());
|
|
}
|
|
}
|
|
else if (DisplayComponent)
|
|
{
|
|
DisplayComponent->SetHiddenInGame(true, /*bPropagateToChildren =*/false);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace UEMotionController {
|
|
// 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;
|
|
};
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
bool UMotionControllerComponent::PollControllerState(FVector& Position, FRotator& Orientation, float WorldToMetersScale)
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
// Cache state from the game thread for use on the render thread
|
|
const AActor* MyOwner = GetOwner();
|
|
bHasAuthority = MyOwner->HasLocalNetOwner();
|
|
|
|
}
|
|
|
|
if(bHasAuthority)
|
|
{
|
|
UEMotionController::FScopeLockOptional LockOptional;
|
|
|
|
TArray<IMotionController*> MotionControllers;
|
|
if (IsInGameThread())
|
|
{
|
|
MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName());
|
|
{
|
|
FScopeLock Lock(&PolledMotionControllerMutex);
|
|
PolledMotionController_GameThread = nullptr;
|
|
}
|
|
}
|
|
else if (IsInRenderingThread())
|
|
{
|
|
LockOptional.Lock(&PolledMotionControllerMutex);
|
|
if (PolledMotionController_RenderThread != nullptr)
|
|
{
|
|
MotionControllers.Add(PolledMotionController_RenderThread);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are in some other thread we can't use the game thread code, because the ModularFeature access isn't threadsafe.
|
|
// The render thread code might work, or not.
|
|
// Let's do the fully safe locking version, and assert because this case is not expected.
|
|
checkNoEntry();
|
|
IModularFeatures::FScopedLockModularFeatureList FeatureListLock;
|
|
MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName());
|
|
}
|
|
|
|
for (auto MotionController : MotionControllers)
|
|
{
|
|
if (MotionController == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource);
|
|
if (MotionController->GetControllerOrientationAndPosition(PlayerIndex, MotionSource, Orientation, Position, WorldToMetersScale))
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
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 (MotionSource == FXRMotionControllerBase::HMDSourceId)
|
|
{
|
|
IXRTrackingSystem* TrackingSys = GEngine->XRSystem.Get();
|
|
if (TrackingSys)
|
|
{
|
|
FQuat OrientationQuat;
|
|
if (TrackingSys->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, OrientationQuat, Position))
|
|
{
|
|
Orientation = OrientationQuat.Rotator();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UMotionControllerComponent::OnModularFeatureUnregistered(const FName& Type, class IModularFeature* ModularFeature)
|
|
{
|
|
FScopeLock Lock(&PolledMotionControllerMutex);
|
|
|
|
if (ModularFeature == PolledMotionController_GameThread)
|
|
{
|
|
PolledMotionController_GameThread = nullptr;
|
|
}
|
|
if (ModularFeature == PolledMotionController_RenderThread)
|
|
{
|
|
PolledMotionController_RenderThread = nullptr;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
UMotionControllerComponent::FViewExtension::FViewExtension(const FAutoRegister& AutoRegister, UMotionControllerComponent* InMotionControllerComponent)
|
|
: FSceneViewExtensionBase(AutoRegister)
|
|
, MotionControllerComponent(InMotionControllerComponent)
|
|
{}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::FViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
|
|
{
|
|
if (!MotionControllerComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set up the late update state for the controller component
|
|
LateUpdate.Setup(MotionControllerComponent->CalcNewComponentToWorld(FTransform()), MotionControllerComponent, false);
|
|
}
|
|
|
|
//=============================================================================
|
|
void UMotionControllerComponent::FViewExtension::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily)
|
|
{
|
|
if (!MotionControllerComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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->RenderThreadRelativeTransform.GetTranslation();
|
|
FRotator Orientation = MotionControllerComponent->RenderThreadRelativeTransform.GetRotation().Rotator();
|
|
if (!MotionControllerComponent->PollControllerState(Position, Orientation, WorldToMetersScale))
|
|
{
|
|
return;
|
|
}
|
|
|
|
OldTransform = MotionControllerComponent->RenderThreadRelativeTransform;
|
|
NewTransform = FTransform(Orientation, Position, MotionControllerComponent->RenderThreadComponentScale);
|
|
} // Release the lock on the MotionControllerComponent
|
|
|
|
// Tell the late update manager to apply the offset to the scene components
|
|
LateUpdate.Apply_RenderThread(InViewFamily.Scene, OldTransform, NewTransform);
|
|
}
|
|
|
|
bool UMotionControllerComponent::FViewExtension::IsActiveThisFrame_Internal(const FSceneViewExtensionContext&) const
|
|
{
|
|
check(IsInGameThread());
|
|
return MotionControllerComponent && !MotionControllerComponent->bDisableLowLatencyUpdate && CVarEnableMotionControllerLateUpdate.GetValueOnGameThread();
|
|
}
|
|
|
|
float UMotionControllerComponent::GetParameterValue(FName InName, bool& bValueFound)
|
|
{
|
|
if (InUseMotionController)
|
|
{
|
|
return InUseMotionController->GetCustomParameterValue(MotionSource, InName, bValueFound);
|
|
}
|
|
bValueFound = false;
|
|
return 0.f;
|
|
}
|
|
|
|
FVector UMotionControllerComponent::GetHandJointPosition(int jointIndex, bool& bValueFound)
|
|
{
|
|
FVector outPosition;
|
|
if (InUseMotionController && InUseMotionController->GetHandJointPosition(MotionSource, jointIndex, outPosition))
|
|
{
|
|
bValueFound = true;
|
|
return outPosition;
|
|
}
|
|
else
|
|
{
|
|
bValueFound = false;
|
|
return FVector::ZeroVector;
|
|
}
|
|
}
|
|
|
|
|
|
void UMotionControllerComponent::OnDisplayModelLoaded(UPrimitiveComponent* InDisplayComponent)
|
|
{
|
|
if (InDisplayComponent == DisplayComponent || DisplayModelLoadState == EModelLoadStatus::Pending)
|
|
{
|
|
if (InDisplayComponent)
|
|
{
|
|
const int32 MatCount = FMath::Min(InDisplayComponent->GetNumMaterials(), DisplayMeshMaterialOverrides.Num());
|
|
for (int32 MatIndex = 0; MatIndex < MatCount; ++MatIndex)
|
|
{
|
|
InDisplayComponent->SetMaterial(MatIndex, DisplayMeshMaterialOverrides[MatIndex]);
|
|
}
|
|
}
|
|
DisplayModelLoadState = EModelLoadStatus::Complete;
|
|
}
|
|
}
|