// Copyright 1998-2017 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" 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 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 //============================================================================= 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; Hand = EControllerHand::Left; bDisableLowLatencyUpdate = false; bHasAuthority = false; bAutoActivate = true; } //============================================================================= UMotionControllerComponent::~UMotionControllerComponent() { 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; } if (GEngine) { GEngine->ViewExtensions.Remove(ViewExtension); } } ViewExtension.Reset(); } void UMotionControllerComponent::SendRenderTransform_Concurrent() { RenderThreadRelativeTransform = GetRelativeTransform(); RenderThreadComponentScale = GetComponentScale(); Super::SendRenderTransform_Concurrent(); } //============================================================================= void UMotionControllerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (bIsActive) { FVector Position; FRotator Orientation; float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f; bTracked = PollControllerState(Position, Orientation, WorldToMeters); if (bTracked) { SetRelativeLocationAndRotation(Position, Orientation); } if (!ViewExtension.IsValid() && GEngine) { TSharedPtr< FViewExtension, ESPMode::ThreadSafe > NewViewExtension(new FViewExtension(this)); ViewExtension = NewViewExtension; GEngine->ViewExtensions.Add(ViewExtension); } } } //============================================================================= 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(); const APawn* MyPawn = Cast(MyOwner); bHasAuthority = MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner->Role == ENetRole::ROLE_Authority); } if ((PlayerIndex != INDEX_NONE) && bHasAuthority) { TArray MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations( IMotionController::GetModularFeatureName() ); for( auto MotionController : MotionControllers ) { if ((MotionController != nullptr) && MotionController->GetControllerOrientationAndPosition(PlayerIndex, Hand, Orientation, Position, WorldToMetersScale)) { CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, Hand); return true; } } } return false; } //============================================================================= void UMotionControllerComponent::FViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily) { if (!MotionControllerComponent) { return; } FScopeLock ScopeLock(&CritSect); if (!MotionControllerComponent || MotionControllerComponent->bDisableLowLatencyUpdate || !CVarEnableMotionControllerLateUpdate.GetValueOnGameThread()) { return; } LateUpdatePrimitives.Reset(); GatherLateUpdatePrimitives(MotionControllerComponent, LateUpdatePrimitives); } //============================================================================= void UMotionControllerComponent::FViewExtension::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) { if (!MotionControllerComponent) { return; } FScopeLock ScopeLock(&CritSect); if (!MotionControllerComponent || MotionControllerComponent->bDisableLowLatencyUpdate || !CVarEnableMotionControllerLateUpdate.GetValueOnRenderThread()) { 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->PollControllerState(Position, Orientation, WorldToMetersScale)) { return; } if (LateUpdatePrimitives.Num()) { // Calculate the late update transform that will rebase all children proxies within the frame of reference const FTransform OldLocalToWorldTransform = MotionControllerComponent->CalcNewComponentToWorld(MotionControllerComponent->RenderThreadRelativeTransform); const FTransform NewLocalToWorldTransform = MotionControllerComponent->CalcNewComponentToWorld(FTransform(Orientation, Position, MotionControllerComponent->RenderThreadComponentScale)); const FMatrix LateUpdateTransform = (OldLocalToWorldTransform.Inverse() * NewLocalToWorldTransform).ToMatrixWithScale(); // Apply delta to the affected scene proxies for (auto PrimitiveInfo : LateUpdatePrimitives) { FPrimitiveSceneInfo* RetrievedSceneInfo = InViewFamily.Scene->GetPrimitiveSceneInfo(*PrimitiveInfo.IndexAddress); FPrimitiveSceneInfo* CachedSceneInfo = PrimitiveInfo.SceneInfo; // If the retrieved scene info is different than our cached scene info then the primitive was removed from the scene if (CachedSceneInfo == RetrievedSceneInfo && CachedSceneInfo->Proxy) { CachedSceneInfo->Proxy->ApplyLateUpdateTransform(LateUpdateTransform); } } LateUpdatePrimitives.Reset(); } } void UMotionControllerComponent::FViewExtension::GatherLateUpdatePrimitives(USceneComponent* Component, TArray& Primitives) { // If a scene proxy is present, cache it UPrimitiveComponent* PrimitiveComponent = dynamic_cast(Component); if (PrimitiveComponent && PrimitiveComponent->SceneProxy) { FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveComponent->SceneProxy->GetPrimitiveSceneInfo(); if (PrimitiveSceneInfo) { LateUpdatePrimitiveInfo PrimitiveInfo; PrimitiveInfo.IndexAddress = PrimitiveSceneInfo->GetIndexAddress(); PrimitiveInfo.SceneInfo = PrimitiveSceneInfo; Primitives.Add(PrimitiveInfo); } } // Gather children proxies const int32 ChildCount = Component->GetNumChildrenComponents(); for (int32 ChildIndex = 0; ChildIndex < ChildCount; ++ChildIndex) { USceneComponent* ChildComponent = Component->GetChildComponent(ChildIndex); if (!ChildComponent) { continue; } GatherLateUpdatePrimitives(ChildComponent, Primitives); } }