diff --git a/Source/StudyFrameworkPlugin/Private/GazeTracking/SFGazeTracker.cpp b/Source/StudyFrameworkPlugin/Private/GazeTracking/SFGazeTracker.cpp index 615f974285bbd220573f5d032064aabf8bb6e52c..9838a59422f8f2b8abb1982e5fcb31f7916d5f09 100644 --- a/Source/StudyFrameworkPlugin/Private/GazeTracking/SFGazeTracker.cpp +++ b/Source/StudyFrameworkPlugin/Private/GazeTracking/SFGazeTracker.cpp @@ -1,5 +1,5 @@ #include "GazeTracking/SFGazeTracker.h" - +#include "Logging/SFLoggingBPLibrary.h" #include "SFGameInstance.h" #include "GazeTracking/SFGazeTarget.h" #include "GazeTracking/SFGazeTargetActor.h" @@ -26,7 +26,7 @@ void USFGazeTracker::Init(EGazeTrackerMode Mode) } else { - USFGameInstance::Get()->LogComment("No Vive Pro Eye present, use head rotation only for gaze tracking."); + USFLoggingBPLibrary::LogComment("No Vive Pro Eye present, use head rotation only for gaze tracking.", true); } #else FSFUtils::OpenMessageBox("SRanipal Plugin is not present, cannot use eye tracking! Check out, e.g., StudyFramework Wiki where to get it", true); @@ -34,10 +34,11 @@ void USFGazeTracker::Init(EGazeTrackerMode Mode) } } -FGazeRay USFGazeTracker::GetGazeDirection() +FGazeRay USFGazeTracker::GetLocalGazeDirection() { - if(!IsTrackingEyes()) + if(!bEyeTrackingStarted) { + // if no eye tracker is used we always "look ahead" FGazeRay GazeRay; GazeRay.Origin = FVector::ZeroVector; GazeRay.Direction = FVector::ForwardVector;; @@ -54,29 +55,52 @@ FGazeRay USFGazeTracker::GetGazeDirection() return GazeRay; } -FString USFGazeTracker::GetCurrentGazeTarget() +FGazeRay USFGazeTracker::GetWorldGazeDirection() { - FString GazedAtTarget = ""; - const float Distance = 1000.0f; - - FGazeRay GazeRay = GetGazeDirection(); - // if no eye tracker is used we always "look ahead" - // GazeDirection = FVector(1,0,0); + FGazeRay LocalGazeRay = GetLocalGazeDirection(); UWorld* World = USFGameInstance::Get()->GetWorld(); //the gaze ray is relative to the HMD const APlayerCameraManager* CamManager = World->GetFirstPlayerController()-> - PlayerCameraManager; + PlayerCameraManager; const FVector CameraLocation = CamManager->GetCameraLocation(); const FRotator CameraRotation = CamManager->GetCameraRotation(); - const FVector RayCastOrigin = CameraLocation + CameraRotation.RotateVector(GazeRay.Origin); - const FVector RayCastEnd = (CameraRotation.RotateVector(GazeRay.Direction) * Distance) + RayCastOrigin; + + FGazeRay GazeRay; + GazeRay.Origin = CameraLocation + CameraRotation.RotateVector(LocalGazeRay.Origin); + GazeRay.Direction = CameraRotation.RotateVector(LocalGazeRay.Direction); + + return GazeRay; +} + +FString USFGazeTracker::GetCurrentGazeTarget() +{ + FString GazedAtTarget = ""; + const float Distance = 1000.0f; + + FGazeRay GazeRay = GetWorldGazeDirection(); + + + const FVector RayCastOrigin = GazeRay.Origin; + const FVector RayCastEnd = (GazeRay.Direction * Distance) + RayCastOrigin; //FSFUtils::Log("Cast Ray from "+RayCastOrigin.ToString()+" to "+RayCastEnd.ToString()); FHitResult HitResult; - World->LineTraceSingleByChannel(HitResult, RayCastOrigin, RayCastEnd, EYE_TRACKING_TRACE_CHANNEL); + UWorld* World = USFGameInstance::Get()->GetWorld(); + FCollisionQueryParams QueryParams; + QueryParams.AddIgnoredActor(World->GetFirstPlayerController()->AcknowledgedPawn); + QueryParams.AddIgnoredActor(USFGameInstance::Get()->GetHUD()->GetHUDHelper()); + + World->LineTraceSingleByChannel(HitResult, RayCastOrigin, RayCastEnd, EYE_TRACKING_TRACE_CHANNEL, QueryParams); + + if (bDebugRenderRayTraces) + { + //this line trace is more comfortable for debug drawing, however has problems with channel tracing, so we use LineTraceSingleByChannel() above + FHitResult TmpHitRes; + UKismetSystemLibrary::LineTraceSingle(World, RayCastOrigin, RayCastEnd, ETraceTypeQuery::TraceTypeQuery4, false, {}, EDrawDebugTrace::ForDuration, TmpHitRes, true); + } if (HitResult.bBlockingHit) @@ -104,17 +128,22 @@ FString USFGazeTracker::GetCurrentGazeTarget() void USFGazeTracker::LaunchCalibration() { #ifdef WITH_SRANIPAL - //TODO: not tested yet! ViveSR::anipal::Eye::LaunchEyeCalibration(nullptr); #endif } bool USFGazeTracker::IsTrackingEyes() { - //TODO: maybe use - //#ifdef WITH_SRANIPAL - //ViveSR::anipal::AnipalStatus Status; - //int Error = ViveSR::anipal::GetStatus(ViveSR::anipal::Eye::ANIPAL_TYPE_EYE_V2, &Status); - //#endif - return bEyeTrackingStarted; + if (!bEyeTrackingStarted) + return false; + +#ifdef WITH_SRANIPAL + ViveSR::anipal::Eye::EyeData_v2 Data; + ViveSR::anipal::Eye::GetEyeData_v2(&Data); + + //no_user apparently is true, when a user is there... (weird but consistent) + return Data.no_user; +#endif + + return true; } diff --git a/Source/StudyFrameworkPlugin/Private/HUD/SFMasterHUD.cpp b/Source/StudyFrameworkPlugin/Private/HUD/SFMasterHUD.cpp index 516bbd0361f3dd43cd962d494902a31c397bb359..70cb6b3a7d01edc16bcfda71db5ad32f18b22948 100644 --- a/Source/StudyFrameworkPlugin/Private/HUD/SFMasterHUD.cpp +++ b/Source/StudyFrameworkPlugin/Private/HUD/SFMasterHUD.cpp @@ -243,3 +243,7 @@ void ASFMasterHUD::OnShowConditionsButtonPressed() } } } + +const ASFHMDSpectatorHUDHelp* ASFMasterHUD::GetHUDHelper() { + return HMDHUDHelper; +} diff --git a/Source/StudyFrameworkPlugin/Private/Logging/SFLogObject.cpp b/Source/StudyFrameworkPlugin/Private/Logging/SFLogObject.cpp index 9fd1adc7703af844e628de2238e8520fdc7b1bc7..53568658e6068dd04b368f9feb31c27b08fcf33c 100644 --- a/Source/StudyFrameworkPlugin/Private/Logging/SFLogObject.cpp +++ b/Source/StudyFrameworkPlugin/Private/Logging/SFLogObject.cpp @@ -50,7 +50,7 @@ void USFLogObject::Initialize() { } // NOTE: When changing header row, update output (see below) -void USFLogObject::LogHeaderRows() { +void USFLogObject::WritePositionLogHeaderRow() { FString PositionLogHeader = FString("ElapsedTime") + "\t" + FString("LogName") + "\t" + FString("Condition") + @@ -63,7 +63,7 @@ void USFLogObject::LogHeaderRows() { UniLog.Log(PositionLogHeader, "PositionLog"); } -void USFLogObject::LoggingLoopsLogToFile() { +void USFLogObject::WritePositionLogToFile() { if (!USFGameInstance::Get() || !USFGameInstance::Get()->GetLogObject()) { return; @@ -73,14 +73,14 @@ void USFLogObject::LoggingLoopsLogToFile() { if (ComponentLoggingInfo.LogNextTick == true) { ComponentLoggingInfo.LogNextTick = false; //When starting in Debug-Mode (i.e. not through the HUD) no condition is defined. - FString currentCondition = USFGameInstance::Get()->GetParticipant()->GetCurrentCondition() ? + FString CurrentCondition = USFGameInstance::Get()->GetParticipant()->GetCurrentCondition() ? USFGameInstance::Get()->GetParticipant()->GetCurrentCondition()->UniqueName : FString("Debug"); // NOTE: When changing output, update header row (see above) FString out = USFGameInstance::Get()->GetParticipant()->GetCurrentTime() + "\t" + ComponentLoggingInfo.LogName + - "\t" + currentCondition + + "\t" + CurrentCondition + "\t" + FString::Printf(TEXT("%.3f"), ComponentLoggingInfo.ComponentToLog->GetComponentLocation().X) + "\t" + FString::Printf(TEXT("%.3f"), ComponentLoggingInfo.ComponentToLog->GetComponentLocation().Y) + "\t" + FString::Printf(TEXT("%.3f"), ComponentLoggingInfo.ComponentToLog->GetComponentLocation().Z) + @@ -99,6 +99,45 @@ void USFLogObject::LoggingLoopsLogToFile() { } } +// NOTE: When changing header row, update output (see below) +void USFLogObject::WriteGazeTrackingLogHeaderRow() { + FString GazeTrackingLogHeader = FString("ElapsedTime") + + "\t" + FString("Condition") + + "\t" + FString("TrackingEyes") + + "\t" + FString("GazeTarget") + + "\t" + FString("Gaze-Origin-X-Y-Z") + + "\t" + FString("Gaze-Direction-X-Y-Z"); + UniLog.Log(GazeTrackingLogHeader, "GazeTrackingLog"); +} + +void USFLogObject::WriteGazeTrackingLogToFile() { + if (!USFGameInstance::Get() || !USFGameInstance::Get()->GetGazeTracker()) + { + return; + } + USFGazeTracker* GazeTracker = USFGameInstance::Get()->GetGazeTracker(); + FString GazeTarget = GazeTracker->GetCurrentGazeTarget() == "" ? "-" : GazeTracker->GetCurrentGazeTarget(); + //When starting in Debug-Mode (i.e. not through the HUD) no condition is defined. + FString CurrentCondition = USFGameInstance::Get()->GetParticipant()->GetCurrentCondition() ? + USFGameInstance::Get()->GetParticipant()->GetCurrentCondition()->UniqueName : + FString("Debug"); + FString isTrackingEyes = USFGameInstance::Get()->GetGazeTracker()->IsTrackingEyes() ? "1" : "0"; + // NOTE: When changing output, update header row (see above) + FString out = USFGameInstance::Get()->GetParticipant()->GetCurrentTime() + + "\t" + CurrentCondition + + "\t" + isTrackingEyes + + "\t" + GazeTarget + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Origin.X) + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Origin.Y) + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Origin.Z) + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Direction.X) + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Direction.Y) + + "\t" + FString::Printf(TEXT("%.3f"), GazeTracker->GetWorldGazeDirection().Direction.Z); + if (UniLog.GetLogStream("GazeTrackingLog")) + { + UniLog.Log(out, "GazeTrackingLog"); + } +} void USFLogObject::SetLoggingLoopsActive(bool LoggingLoopsActive) { bLoggingLoopsActive = LoggingLoopsActive; diff --git a/Source/StudyFrameworkPlugin/Private/Logging/SFLoggingUtils.cpp b/Source/StudyFrameworkPlugin/Private/Logging/SFLoggingUtils.cpp index 902807a42a747de67fa0b9c5f3bad2f08bad91eb..0968565382b0eadade7454220cd06ec46b712070 100644 --- a/Source/StudyFrameworkPlugin/Private/Logging/SFLoggingUtils.cpp +++ b/Source/StudyFrameworkPlugin/Private/Logging/SFLoggingUtils.cpp @@ -34,7 +34,7 @@ void FSFLoggingUtils::SetupLoggingStreams() ILogStream* SFLog = UniLog.NewLogStream("SFLog", "StudyFramework/DebuggingLogs", "SFLog.txt", false); SFLog->SetLogToDefaultLog(true); - //ParticipantLog, PositionLog are set up in Participant init function + //ParticipantLog, PositionLog, GazeTrackingLog are set up in SFParticipant::SetupLoggingStreams() function ILogStream* SFErrorLog = UniLog.NewLogStream("SFErrorLog", "StudyFramework/DebuggingLogs", "SFLog.txt", false); SFErrorLog->SetLogToDefaultLog(true); diff --git a/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp b/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp index 544162630c1c918571bf89d29101e9c3c2d54f62..830f7a03da55920beed38316fd09d01eb58c58ea 100644 --- a/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFGameInstance.cpp @@ -131,10 +131,14 @@ void USFGameInstance::RestoreLastParticipantForDebugStart(USFCondition* InStartC const int ParticipantID = USFParticipant::GetLastParticipantId(); Participant = NewObject<USFParticipant>(this, FName(TEXT("Participant_") + FString::FromInt(ParticipantID))); + + StudySetup = USFParticipant::GetLastParticipantSetup(); + Participant->Initialize(ParticipantID); + Participant->SetupLoggingStreams(StudySetup->UseGazeTracker != EGazeTrackerMode::NotTracking); Participant->LoadConditionsFromJson(); - StudySetup = USFParticipant::GetLastParticipantSetup(); + InitFadeHandler(StudySetup->FadeConfig); StartCondition = InStartCondition; @@ -240,6 +244,7 @@ void USFGameInstance::PrepareWithStudySetup(ASFStudySetup* Setup) Participant = NewObject<USFParticipant>(this, FName(TEXT("Participant_") + FString::FromInt(ParticipantID))); Participant->Initialize(ParticipantID); + Participant->SetupLoggingStreams(StudySetup->UseGazeTracker != EGazeTrackerMode::NotTracking); Participant->SetStudyConditions(Conditions); if (bRecoverParticipantData) @@ -531,7 +536,8 @@ USFParticipant* USFGameInstance::GetParticipant() const bool USFGameInstance::LogTick(float DeltaSeconds) { if (LogObject->bLoggingLoopsActive){ - LogObject->LoggingLoopsLogToFile(); + LogObject->WritePositionLogToFile(); + LogObject->WriteGazeTrackingLogToFile(); } return true; } diff --git a/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp b/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp index fe59cf900641b3b4955a1573547a99e87f7838c8..96a4a2465fb94d82b8676faa78e1b53f40dfdb06 100644 --- a/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFParticipant.cpp @@ -18,30 +18,37 @@ USFParticipant::~USFParticipant() { } -bool USFParticipant::Initialize(int Participant, bool bSetIdOnly /*= false*/) +bool USFParticipant::Initialize(int Participant) { ParticipantID = Participant; - if(bSetIdOnly) - { - return true; - } + StartTime = FPlatformTime::Seconds(); + + return true; +} +void USFParticipant::SetupLoggingStreams(bool bGazeTracking) +{ const FString Timestamp = FDateTime::Now().ToString(); const FString Filename = "LogParticipant-" + FString::FromInt(ParticipantID) + "_" + Timestamp + ".txt"; ILogStream* ParticipantLog = UniLog.NewLogStream("ParticipantLog", "StudyFramework/StudyLogs/ParticipantLogs", - Filename, false); + Filename, false); ILogStream* PositionLog = UniLog.NewLogStream("PositionLog", "StudyFramework/StudyLogs/PositionLogs", - "Position"+Filename, false); + "Position" + Filename, false); + if (bGazeTracking) + { + ILogStream* GazeTrackingLog = UniLog.NewLogStream("GazeTrackingLog", "StudyFramework/StudyLogs/GazeTrackingLogs", + "GazeTracking" + Filename, false); + } if (USFGameInstance::Get()) { - USFGameInstance::Get()->GetLogObject()->LogHeaderRows(); + USFGameInstance::Get()->GetLogObject()->WritePositionLogHeaderRow(); + USFGameInstance::Get()->GetLogObject()->WriteGazeTrackingLogHeaderRow(); } else { FSFLoggingUtils::Log("GameInstance not set up yet, no header rows are written to participant logs."); } - return true; } void USFParticipant::SetStudyConditions(TArray<USFCondition*> NewConditions) diff --git a/Source/StudyFrameworkPlugin/Private/SFStudySetup.cpp b/Source/StudyFrameworkPlugin/Private/SFStudySetup.cpp index 5ac661fa18e6d8431ae03a6e26d00904fe871686..684c0af6f4d8f3405be0d22dc3c387ca2a9fb390 100644 --- a/Source/StudyFrameworkPlugin/Private/SFStudySetup.cpp +++ b/Source/StudyFrameworkPlugin/Private/SFStudySetup.cpp @@ -148,7 +148,7 @@ void ASFStudySetup::GenerateTestStudyRuns() const { const TArray<USFCondition*> Conditions = GetAllConditionsForRun(ParticipantID); USFParticipant* TmpParticipant = NewObject<USFParticipant>(); - TmpParticipant->Initialize(ParticipantID, true); + TmpParticipant->Initialize(ParticipantID); TmpParticipant->SetStudyConditions(Conditions); //this also saves it to json } } diff --git a/Source/StudyFrameworkPlugin/Public/GazeTracking/SFGazeTracker.h b/Source/StudyFrameworkPlugin/Public/GazeTracking/SFGazeTracker.h index 96b9bc0ef29e28a141b06435086a33987bef0ae0..0aee44bfbe668752aa01845d6a2f379aae629704 100644 --- a/Source/StudyFrameworkPlugin/Public/GazeTracking/SFGazeTracker.h +++ b/Source/StudyFrameworkPlugin/Public/GazeTracking/SFGazeTracker.h @@ -30,20 +30,28 @@ class STUDYFRAMEWORKPLUGIN_API USFGazeTracker : public UObject public: void Init(EGazeTrackerMode Mode); - //returns pair of Origin and Direction, relative to the head! + //returns pair of Origin and Direction, in world coordinates UFUNCTION(BlueprintCallable) - FGazeRay GetGazeDirection(); + FGazeRay GetWorldGazeDirection(); //returns the name of the gaze target or empty string if none found or not tracking UFUNCTION(BlueprintCallable) FString GetCurrentGazeTarget(); + //returns pair of Origin and Direction, relative to the head! + UFUNCTION(BlueprintCallable) + FGazeRay GetLocalGazeDirection(); + UFUNCTION(BlueprintCallable) void LaunchCalibration(); + //whether currently the eyes are tracked, otherwise head-forward is used (this can change during execution if the HMD is taken off etc.) UFUNCTION(BlueprintCallable) bool IsTrackingEyes(); + UPROPERTY(BlueprintReadWrite) + bool bDebugRenderRayTraces = false; + private: bool bEyeTrackingStarted = false; diff --git a/Source/StudyFrameworkPlugin/Public/HUD/SFMasterHUD.h b/Source/StudyFrameworkPlugin/Public/HUD/SFMasterHUD.h index a674f7537717505955c1b1a9d61631183c3adac9..5ab2e32cf670d73bab12c9c5c73d77758c8ece43 100644 --- a/Source/StudyFrameworkPlugin/Public/HUD/SFMasterHUD.h +++ b/Source/StudyFrameworkPlugin/Public/HUD/SFMasterHUD.h @@ -61,6 +61,8 @@ public: UFUNCTION() void OnShowConditionsButtonPressed(); + const ASFHMDSpectatorHUDHelp* GetHUDHelper(); + protected: UPROPERTY(VisibleAnywhere) USFHUDWidget* HUDWidget; diff --git a/Source/StudyFrameworkPlugin/Public/Logging/SFLogObject.h b/Source/StudyFrameworkPlugin/Public/Logging/SFLogObject.h index d96088d7a5af7ca54e58d08f23db9ee89f8ae309..b44a2bbb8106acfc012ca459078870c6579211a6 100644 --- a/Source/StudyFrameworkPlugin/Public/Logging/SFLogObject.h +++ b/Source/StudyFrameworkPlugin/Public/Logging/SFLogObject.h @@ -65,7 +65,14 @@ public: UFUNCTION() bool GetLoggingLoopsActive(); UFUNCTION() - static void LogHeaderRows(); + static void WritePositionLogHeaderRow(); UFUNCTION() - static void LoggingLoopsLogToFile(); + static void WritePositionLogToFile(); + + UFUNCTION() + static void WriteGazeTrackingLogHeaderRow(); + + UFUNCTION() + static void WriteGazeTrackingLogToFile(); + }; diff --git a/Source/StudyFrameworkPlugin/Public/SFParticipant.h b/Source/StudyFrameworkPlugin/Public/SFParticipant.h index 6294d62d61c37c228a487f016ea520db31d4048e..6a0f89e5c958aa848d5fa53aadad54a2782c71c6 100644 --- a/Source/StudyFrameworkPlugin/Public/SFParticipant.h +++ b/Source/StudyFrameworkPlugin/Public/SFParticipant.h @@ -28,7 +28,8 @@ public: USFParticipant(); ~USFParticipant(); - bool Initialize(int Participant, bool bSetIdOnly = false); + bool Initialize(int Participant); + void SetupLoggingStreams(bool bGazeTracking); void SetStudyConditions(TArray<USFCondition*> NewConditions); bool StartStudy();