// Copyright Epic Games, Inc. All Rights Reserved. #include "XRCreativeAvatar.h" #include "XRCreativeGameMode.h" #include "XRCreativeITFComponent.h" #include "XRCreativeLog.h" #include "XRCreativePointerComponent.h" #include "XRCreativeToolset.h" #include "Components/SkeletalMeshComponent.h" #include "Components/WidgetComponent.h" #include "Components/WidgetInteractionComponent.h" #include "Engine/Level.h" #include "Engine/LocalPlayer.h" #include "Engine/InputDelegateBinding.h" #include "EnhancedInputComponent.h" #include "EnhancedInputSubsystems.h" #include "Framework/Application/SlateApplication.h" #include "GameFramework/InputSettings.h" #include "GameFramework/PlayerController.h" #include "Haptics/HapticFeedbackEffect_Base.h" #include "InputMappingContext.h" #include "IXRTrackingSystem.h" #include "LevelSequenceActor.h" #include "LevelSequencePlayer.h" #include "Logging/LogMacros.h" #include "Modules/ModuleManager.h" #include "MotionControllerComponent.h" #include "XRMotionControllerBase.h" #if WITH_EDITOR # include "Editor.h" # include "EnhancedInputEditorSubsystem.h" # include "IConcertClient.h" # include "IConcertClientSequencerManager.h" # include "IConcertSyncClient.h" # include "IMultiUserClientModule.h" # include "IVREditorModule.h" # include "VREditorModeBase.h" # include "XRCreativeSettings.h" #endif AXRCreativeAvatar::AXRCreativeAvatar(const FObjectInitializer& ObjectInitializer) { USceneComponent* SceneRoot = CreateDefaultSubobject("Root"); SetRootComponent(SceneRoot); LeftController = CreateDefaultSubobject("LeftController"); LeftController->SetupAttachment(GetRootComponent()); LeftController->bTickInEditor = true; LeftController->MotionSource = "Left"; LeftControllerAim = CreateDefaultSubobject("LeftControllerAim"); LeftControllerAim->SetupAttachment(GetRootComponent()); LeftControllerAim->bTickInEditor = true; LeftControllerAim->MotionSource = "LeftAim"; LeftControllerPointer = CreateDefaultSubobject("LeftControllerPointer"); LeftControllerPointer->SetupAttachment(LeftControllerAim); LeftControllerPointer->bTickInEditor = true; LeftControllerModel = CreateDefaultSubobject("LeftControllerSkeletalMeshComponent"); LeftControllerModel->SetupAttachment(LeftController); LeftControllerModel->bTickInEditor = true; LeftControllerModel->SetCastShadow(false); RightController = CreateDefaultSubobject("RightController"); RightController->SetupAttachment(GetRootComponent()); RightController->bTickInEditor = true; RightController->MotionSource = "Right"; RightControllerAim = CreateDefaultSubobject("RightControllerAim"); RightControllerAim->SetupAttachment(GetRootComponent()); RightControllerAim->bTickInEditor = true; RightControllerAim->MotionSource = "RightAim"; RightControllerPointer = CreateDefaultSubobject("RightControllerPointer"); RightControllerPointer->SetupAttachment(RightControllerAim); RightControllerPointer->bTickInEditor = true; RightControllerModel = CreateDefaultSubobject("RightControllerSkeletalMeshComponent"); RightControllerModel->SetupAttachment(RightController); RightControllerModel->bTickInEditor = true; RightControllerModel->SetCastShadow(false); MenuWidget = CreateDefaultSubobject("Widget"); MenuWidget->SetupAttachment(LeftControllerAim); MenuWidget->bTickInEditor = true; MenuWidget->SetEditTimeUsable(true); MenuWidget->SetCastShadow(false); WidgetInteraction = CreateDefaultSubobject("WidgetInteraction"); WidgetInteraction->SetupAttachment(RightControllerAim); WidgetInteraction->bTickInEditor = true; WidgetInteraction->bShowDebug = false; WidgetInteraction->InteractionDistance = 50.0; ToolsComponent = CreateDefaultSubobject("ToolsComponent"); ToolsComponent->bTickInEditor = true; ToolsComponent->SetPointerComponent(RightControllerPointer); BaseEyeHeight = 0.0f; } void AXRCreativeAvatar::OnConstruction(const FTransform& InTransform) { Super::OnConstruction(InTransform); if (!HasAnyFlags(RF_ClassDefaultObject) && !InputComponent) { InputComponent = NewObject(this, UInputSettings::GetDefaultInputComponentClass(), TEXT("XRCreativeInput0"), RF_Transient); RegisterInputComponent(); UInputDelegateBinding::BindInputDelegatesWithSubojects(this, InputComponent); } // Force any skeletal mesh components to update their animation if we are running in-editor TInlineComponentArray SkeletalMeshComponents; GetComponents(SkeletalMeshComponents); for (USkeletalMeshComponent* SkeletalMeshComponent : SkeletalMeshComponents) { SkeletalMeshComponent->SetUpdateAnimationInEditor(true); } #if WITH_EDITOR UWorld* World = GetWorld(); const bool bActorsInitialized = World && World->AreActorsInitialized(); if (!bActorsInitialized && !ToolsComponent->HasBeenInitialized()) { // We're in editor, and the base class won't initialize our components, so do it manually. ToolsComponent->InitializeComponent(); } UXRCreativeEditorSettings* Settings = UXRCreativeEditorSettings::GetXRCreativeEditorSettings(); UE_LOG(LogXRCreative, Log, TEXT("Handedness: %s"), *UEnum::GetValueAsString(Settings->Handedness) ); if (Settings->Handedness == EXRCreativeHandedness::Left) { ToolsComponent->SetPointerComponent(LeftControllerPointer); } #endif if (!IsTemplate()) { #if WITH_EDITOR MultiUserStartup(); #endif } } void AXRCreativeAvatar::BeginDestroy() { UnregisterInputComponent(); if (!IsTemplate()) { #if WITH_EDITOR MultiUserShutdown(); #endif } Super::BeginDestroy(); } void AXRCreativeAvatar::Tick(float InDeltaSeconds) { Super::Tick(InDeltaSeconds); ProcessHaptics(InDeltaSeconds); } void AXRCreativeAvatar::BeginPlay() { if (AXRCreativeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) { if (UXRCreativeToolset* ModeToolset = GameMode->GetToolset()) { ConfigureToolset(ModeToolset); } } Super::BeginPlay(); } void AXRCreativeAvatar::GetActorEyesViewPoint(FVector& Location, FRotator& Rotation) const { FTransform HeadTransform = GetHeadTransform(); Location = HeadTransform.GetLocation(); Rotation = HeadTransform.GetRotation().Rotator(); } void AXRCreativeAvatar::ConfigureToolset(UXRCreativeToolset* InToolset) { check(InToolset); ensure(Toolset == nullptr); Toolset = InToolset; for (const FXRCreativeToolEntry& ToolEntry : Toolset->Tools) { if (ensure(ToolEntry.ToolClass)) { UXRCreativeTool* NewTool = NewObject(this, ToolEntry.ToolClass); Tools.Add(NewTool); } } } FTransform AXRCreativeAvatar::GetHeadTransform() const { return GetHeadTransformRoomSpace() * GetActorTransform(); } FTransform AXRCreativeAvatar::GetHeadTransformRoomSpace() const { FQuat RoomSpaceHeadOrientation; FVector RoomSpaceHeadLocation; if (GEngine && GEngine->XRSystem && GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, RoomSpaceHeadOrientation, RoomSpaceHeadLocation)) { return FTransform( RoomSpaceHeadOrientation, RoomSpaceHeadLocation, FVector(1.0f)); } return FTransform(); } bool AXRCreativeAvatar::GetLaserForHand(EControllerHand InHand, FVector& OutLaserStart, FVector& OutLaserEnd) const { UXRCreativePointerComponent* Pointer = nullptr; switch (InHand) { case EControllerHand::Left: Pointer = LeftControllerPointer; break; case EControllerHand::Right: Pointer = RightControllerPointer; break; } if (Pointer && Pointer->IsEnabled()) { const FHitResult& LaserHitResult = Pointer->GetHitResult(); OutLaserStart = Pointer->GetComponentLocation(); OutLaserEnd = LaserHitResult.bBlockingHit ? LaserHitResult.ImpactPoint : LaserHitResult.TraceEnd; return true; } return false; } void AXRCreativeAvatar::SetComponentTickInEditor(UActorComponent* InComponent, bool bInShouldTickInEditor) { if (!InComponent || InComponent->GetOwner() != this) { return; } InComponent->bTickInEditor = bInShouldTickInEditor; } void AXRCreativeAvatar::RegisterObjectForInput(UObject* InObject) { if (IsValid(InputComponent) && IsValid(InObject)) { InputComponent->ClearBindingsForObject(InObject); UInputDelegateBinding::BindInputDelegates(InObject->GetClass(), InputComponent, InObject); } } void AXRCreativeAvatar::UnregisterObjectForInput(UObject* InObject) { if (IsValid(InputComponent) && InObject) { InputComponent->ClearBindingsForObject(InObject); } } void AXRCreativeAvatar::AddInputMappingContext(UInputMappingContext* InContext, int32 InPriority,const FModifyContextOptions Options) { if (IEnhancedInputSubsystemInterface* EnhancedInputSubsystemInterface = GetEnhancedInputSubsystemInterface()) { if (IsValid(InContext)) { EnhancedInputSubsystemInterface->AddMappingContext(InContext, InPriority, Options); } } } void AXRCreativeAvatar::RemoveInputMappingContext(UInputMappingContext* InContext, const FModifyContextOptions Options) { if (IEnhancedInputSubsystemInterface* EnhancedInputSubsystemInterface = GetEnhancedInputSubsystemInterface()) { if (IsValid(InContext)) { EnhancedInputSubsystemInterface->RemoveMappingContext(InContext, Options); } } } void AXRCreativeAvatar::ClearAllInputMappings() { if (IEnhancedInputSubsystemInterface* EnhancedInputSubsystemInterface = GetEnhancedInputSubsystemInterface()) { EnhancedInputSubsystemInterface->ClearAllMappings(); } } //** Cues up a Haptic effect to be processed on tick in ProcessHaptics() // void AXRCreativeAvatar::PlayHapticEffect(UHapticFeedbackEffect_Base* HapticEffect, const int ControllerID, const EControllerHand Hand, float Scale, bool bLoop) { if (HapticEffect) { switch (Hand) { case EControllerHand::Left: ActiveHapticEffect_Left.Reset(); ActiveHapticEffect_Left = MakeShareable(new FActiveHapticFeedbackEffect (HapticEffect, Scale, bLoop)); break; case EControllerHand::Right: ActiveHapticEffect_Right.Reset(); ActiveHapticEffect_Right = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop)); break; default: UE_LOG(LogXRCreative, Warning, TEXT("invalid hand specified (%d) for feedback effect %s. Only left and right hand controllers supported"), (int32)Hand, *HapticEffect->GetName()); } } } void AXRCreativeAvatar::StopHapticEffect(EControllerHand Hand, const int ControllerID) { if (Hand == EControllerHand::Left) { ActiveHapticEffect_Left.Reset(); } else if (Hand == EControllerHand::Right) { ActiveHapticEffect_Right.Reset(); } IInputInterface* InputInterface = FSlateApplication::Get().GetInputInterface(); if (InputInterface) { FHapticFeedbackValues Values( 0.f, 0.f); InputInterface->SetHapticFeedbackValues(ControllerID, (int32)Hand, Values); } } //**TO DO need to add check to see if Game is paused here as happens in PlayerController// void AXRCreativeAvatar::ProcessHaptics(const float DeltaTime) { FHapticFeedbackValues LeftHaptics, RightHaptics; bool bLeftHapticsNeedUpdate = false; bool bRightHapticsNeedUpdate = false; if (ActiveHapticEffect_Left.IsValid()) { const bool bPlaying = ActiveHapticEffect_Left->Update(DeltaTime, LeftHaptics); if (!bPlaying) { ActiveHapticEffect_Left->bLoop ? ActiveHapticEffect_Left->Restart() : ActiveHapticEffect_Left.Reset(); } bLeftHapticsNeedUpdate = true; } if (ActiveHapticEffect_Right.IsValid()) { const bool bPlaying = ActiveHapticEffect_Right->Update(DeltaTime, RightHaptics); if (!bPlaying) { ActiveHapticEffect_Right->bLoop ? ActiveHapticEffect_Right->Restart() : ActiveHapticEffect_Right.Reset(); } bRightHapticsNeedUpdate = true; } if (FSlateApplication::IsInitialized()) { int32 ControllerID = 0; IInputInterface* InputInterface = FSlateApplication::Get().GetInputInterface(); if (InputInterface) { if (bLeftHapticsNeedUpdate) { InputInterface->SetHapticFeedbackValues(ControllerID, (int32) EControllerHand::Left, LeftHaptics); } if (bRightHapticsNeedUpdate) { InputInterface->SetHapticFeedbackValues(ControllerID, (int32) EControllerHand::Right, RightHaptics); } } } } IEnhancedInputSubsystemInterface* AXRCreativeAvatar::GetEnhancedInputSubsystemInterface() const { if (const UWorld* World = GetWorld(); IsValid(World) && World->IsGameWorld()) { if (const ULocalPlayer* FirstLocalPlayer = World->GetFirstLocalPlayerFromController()) { return ULocalPlayer::GetSubsystem(FirstLocalPlayer); } } #if WITH_EDITOR else if (GEditor) { return GEditor->GetEditorSubsystem(); } #endif return nullptr; } void AXRCreativeAvatar::RegisterInputComponent() { // Ensure we start from a clean slate UnregisterInputComponent(); if (const UWorld* World = GetWorld(); IsValid(World) && World->IsGameWorld()) { if (APlayerController* PC = World->GetFirstPlayerController()) { PC->PushInputComponent(InputComponent); bIsInputRegistered = true; } } #if WITH_EDITOR else if (GEditor) { if (UEnhancedInputEditorSubsystem* EditorInputSubsystem = GEditor->GetEditorSubsystem()) { EditorInputSubsystem->PushInputComponent(InputComponent); EditorInputSubsystem->StartConsumingInput(); bIsInputRegistered = true; } } #endif } void AXRCreativeAvatar::UnregisterInputComponent() { // Removes the component from both editor and runtime input systems if possible if (const UWorld* World = GetWorld(); IsValid(World) && World->IsGameWorld()) { if (APlayerController* PC = World->GetFirstPlayerController()) { PC->PopInputComponent(InputComponent); } } #if WITH_EDITOR if (GEditor) { if (UEnhancedInputEditorSubsystem* EditorInputSubsystem = GEditor->GetEditorSubsystem()) { EditorInputSubsystem->PopInputComponent(InputComponent); } } #endif bIsInputRegistered = false; } AActor* AXRCreativeAvatar::InternalSpawnTransientActor( TSubclassOf ActorClass, const FString& ActorName, TOptional> DeferredConstructionCallback) { #if 0 // TODO: Move defer support into UEditorWorldExtension if we want world transitions if (IVREditorModule::IsAvailable()) { UVREditorModeBase* VrMode = IVREditorModule::Get().GetVRModeBase(); if (VrMode && (VrMode->GetWorld() == this->GetWorld())) { return VrMode->SpawnTransientSceneActor(ActorClass, ActorName); } } #endif UWorld* World = GetWorld(); #if WITH_EDITOR const bool bWasWorldPackageDirty = World->GetOutermost()->IsDirty(); #endif FActorSpawnParameters ActorSpawnParameters; ActorSpawnParameters.Name = MakeUniqueObjectName(World->GetCurrentLevel(), ActorClass, *ActorName); ActorSpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; ActorSpawnParameters.ObjectFlags = RF_Transient | RF_DuplicateTransient; if (DeferredConstructionCallback.IsSet()) { // NB: User is responsible for calling FinishSpawning, either // during callback or later on returned actor ActorSpawnParameters.bDeferConstruction = true; } AActor* Actor = World->SpawnActor(ActorClass, ActorSpawnParameters); if (DeferredConstructionCallback.IsSet()) { (*DeferredConstructionCallback)(Actor); } #if WITH_EDITOR // Don't dirty the level file after spawning a transient actor if (!bWasWorldPackageDirty) { World->GetOutermost()->SetDirtyFlag(false); } #endif return Actor; } ALevelSequenceActor* AXRCreativeAvatar::OpenLevelSequence(ULevelSequence* LevelSequence) { if (!ensure(LevelSequence)) { return nullptr; } ALevelSequenceActor* Actor = CastChecked(SpawnTransientActor( ALevelSequenceActor::StaticClass(), TEXT("XRCreativeSequence"), [LevelSequence](AActor* NewActor) { ALevelSequenceActor* NewLSA = CastChecked(NewActor); NewLSA->SetSequence(LevelSequence); NewLSA->InitializePlayer(); NewLSA->FinishSpawning(FTransform::Identity); })); return Actor; } #if WITH_EDITOR void AXRCreativeAvatar::MultiUserStartup() { if (IMultiUserClientModule::IsAvailable()) { if (TSharedPtr ConcertSyncClient = IMultiUserClientModule::Get().GetClient()) { IConcertClientRef ConcertClient = ConcertSyncClient->GetConcertClient(); OnSessionStartupHandle = ConcertClient->OnSessionStartup().AddUObject(this, &AXRCreativeAvatar::HandleSessionStartup); OnSessionShutdownHandle = ConcertClient->OnSessionShutdown().AddUObject(this, &AXRCreativeAvatar::HandleSessionShutdown); if (TSharedPtr ConcertClientSession = ConcertClient->GetCurrentSession()) { HandleSessionStartup(ConcertClientSession.ToSharedRef()); } } } } void AXRCreativeAvatar::MultiUserShutdown() { if (IMultiUserClientModule::IsAvailable()) { if (TSharedPtr ConcertSyncClient = IMultiUserClientModule::Get().GetClient()) { IConcertClientRef ConcertClient = ConcertSyncClient->GetConcertClient(); if (TSharedPtr ConcertClientSession = ConcertClient->GetCurrentSession()) { HandleSessionShutdown(ConcertClientSession.ToSharedRef()); } ConcertClient->OnSessionStartup().Remove(OnSessionStartupHandle); OnSessionStartupHandle.Reset(); ConcertClient->OnSessionShutdown().Remove(OnSessionShutdownHandle); OnSessionShutdownHandle.Reset(); } } } void AXRCreativeAvatar::HandleSessionStartup(TSharedRef InSession) { WeakSession = InSession; //InSession->RegisterCustomEventHandler(this, &AXRCreativeAvatar::HandleConcertEvent); } void AXRCreativeAvatar::HandleSessionShutdown(TSharedRef InSession) { if (TSharedPtr Session = WeakSession.Pin()) { //Session->UnregisterCustomEventHandler(this); } WeakSession.Reset(); } #endif // #if WITH_EDITOR