Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • vr-vis/VR-Group/unreal-development/plugins/unreal-study-framework
1 result
Select Git revision
Loading items
Show changes

Commits on Source 52

Showing
with 271 additions and 77 deletions
No preview for this file type
No preview for this file type
...@@ -12,7 +12,6 @@ void USFConditionListEntry::FillWithCondition(const USFCondition* InCondition) ...@@ -12,7 +12,6 @@ void USFConditionListEntry::FillWithCondition(const USFCondition* InCondition)
Condition = InCondition; Condition = InCondition;
TArray<FString> Data; TArray<FString> Data;
Data.Add(Condition->PhaseName); Data.Add(Condition->PhaseName);
Data.Add(FPaths::GetBaseFilename(Condition->Map));
Data.Add(Condition->GetTimeTaken() <= 0.0 ? "-" : FString::Printf(TEXT("%.2f"), Condition->GetTimeTaken())); Data.Add(Condition->GetTimeTaken() <= 0.0 ? "-" : FString::Printf(TEXT("%.2f"), Condition->GetTimeTaken()));
for (auto Factor : Condition->FactorLevels) for (auto Factor : Condition->FactorLevels)
{ {
...@@ -21,8 +20,8 @@ void USFConditionListEntry::FillWithCondition(const USFCondition* InCondition) ...@@ -21,8 +20,8 @@ void USFConditionListEntry::FillWithCondition(const USFCondition* InCondition)
TextBlockIdToDependentVar.Empty(); TextBlockIdToDependentVar.Empty();
for (auto DependentVar : Condition->DependentVariablesValues) for (auto DependentVar : Condition->DependentVariablesValues)
{ {
TextBlockIdToDependentVar.Add(Data.Num() - 3, DependentVar.Key); TextBlockIdToDependentVar.Add(Data.Num() - 2, DependentVar.Key);
//-3 since the first three elements of Data or not mapped to the text fields //-2 since the first two elements of Data or not mapped to the text fields
Data.Add(DependentVar.Value == "" ? "-" : DependentVar.Value); Data.Add(DependentVar.Value == "" ? "-" : DependentVar.Value);
} }
FillTextsHelper(Data); FillTextsHelper(Data);
...@@ -34,7 +33,6 @@ void USFConditionListEntry::FillAsPhaseHeader(const USFCondition* InCondition) ...@@ -34,7 +33,6 @@ void USFConditionListEntry::FillAsPhaseHeader(const USFCondition* InCondition)
Condition = InCondition; Condition = InCondition;
TArray<FString> Data; TArray<FString> Data;
Data.Add("Phase"); Data.Add("Phase");
Data.Add("Map");
Data.Add("Duration"); Data.Add("Duration");
for (auto Factor : Condition->FactorLevels) for (auto Factor : Condition->FactorLevels)
{ {
...@@ -52,13 +50,12 @@ void USFConditionListEntry::FillAsPhaseHeader(const USFCondition* InCondition) ...@@ -52,13 +50,12 @@ void USFConditionListEntry::FillAsPhaseHeader(const USFCondition* InCondition)
void USFConditionListEntry::FillTextsHelper(const TArray<FString>& Data) void USFConditionListEntry::FillTextsHelper(const TArray<FString>& Data)
{ {
Phase->SetText(FText::FromString(Data[0])); Phase->SetText(FText::FromString(Data[0]));
Map->SetText(FText::FromString(Data[1])); Time->SetText(FText::FromString(Data[1]));
Time->SetText(FText::FromString(Data[2]));
int UsedTexts = 0; int UsedTexts = 0;
TArray<UTextBlock*> Texts = {Text0, Text1, Text2, Text3, Text4, Text5, Text6, Text7}; TArray<UTextBlock*> Texts = {Text0, Text1, Text2, Text3, Text4, Text5, Text6, Text7};
for (int i = 3; i < Data.Num(); ++i) for (int i = 2; i < Data.Num(); ++i)
{ {
if (UsedTexts >= Texts.Num()) if (UsedTexts >= Texts.Num())
{ {
......
...@@ -153,8 +153,6 @@ void ASFMasterHUD::UpdateHUD(USFParticipant* Participant, const FString& Status) ...@@ -153,8 +153,6 @@ void ASFMasterHUD::UpdateHUD(USFParticipant* Participant, const FString& Status)
HUDWidget->SetPhase(Condition->PhaseName); HUDWidget->SetPhase(Condition->PhaseName);
TMap<FString, FString> FactorLevels = Condition->FactorLevels; TMap<FString, FString> FactorLevels = Condition->FactorLevels;
//Add map since it is not by default in FactorLevels
FactorLevels.Add("Map", FPaths::GetBaseFilename(Condition->Map));
HUDWidget->SetCondition(FactorLevels); HUDWidget->SetCondition(FactorLevels);
USFGameInstance::Get()->HUDSavedData = HUDWidget->GetData(); USFGameInstance::Get()->HUDSavedData = HUDWidget->GetData();
......
...@@ -22,7 +22,7 @@ void FSFUtils::OpenMessageBox(const FString Text, const bool bError/*=false*/) ...@@ -22,7 +22,7 @@ void FSFUtils::OpenMessageBox(const FString Text, const bool bError/*=false*/)
return; return;
} }
FSFLoggingUtils::Log(FString("[FVAUtils::OpenMessageBox(ERROR = ") + (bError ? "TRUE" : "FALSE") + FSFLoggingUtils::Log(FString("[FSFUtils::OpenMessageBox(ERROR = ") + (bError ? "TRUE" : "FALSE") +
")]: Opening Message Box with message: " + Text, bError); ")]: Opening Message Box with message: " + Text, bError);
FText Title = FText::FromString(FString(bError ? "ERROR" : "Message")); FText Title = FText::FromString(FString(bError ? "ERROR" : "Message"));
...@@ -47,15 +47,17 @@ TSharedPtr<FJsonObject> FSFUtils::StringToJson(FString String) ...@@ -47,15 +47,17 @@ TSharedPtr<FJsonObject> FSFUtils::StringToJson(FString String)
return Json; return Json;
} }
void FSFUtils::WriteJsonToFile(TSharedPtr<FJsonObject> Json, FString FilenName) void FSFUtils::WriteJsonToFile(TSharedPtr<FJsonObject> Json, FString FilePath)
{ {
FFileHelper::SaveStringToFile(JsonToString(Json), *(FPaths::ProjectDir() + "StudyFramework/" + FilenName)); FilePath = GetStudyFrameworkPath() + FilePath;
FFileHelper::SaveStringToFile(JsonToString(Json), *(FilePath));
} }
TSharedPtr<FJsonObject> FSFUtils::ReadJsonFromFile(FString FilenName) TSharedPtr<FJsonObject> FSFUtils::ReadJsonFromFile(FString FilePath)
{ {
FString JsonString; FString JsonString;
if(!FFileHelper::LoadFileToString(JsonString, *(FPaths::ProjectDir() + "StudyFramework/" + FilenName))) FilePath = GetStudyFrameworkPath() + FilePath;
if(!FFileHelper::LoadFileToString(JsonString, *(FilePath)))
{ {
return nullptr; return nullptr;
} }
...@@ -66,3 +68,8 @@ UWorld* FSFUtils::GetWorld() ...@@ -66,3 +68,8 @@ UWorld* FSFUtils::GetWorld()
{ {
return GEngine->GetWorld(); return GEngine->GetWorld();
} }
FString FSFUtils::GetStudyFrameworkPath()
{
return FPaths::ProjectDir() + "StudyFramework/";
}
...@@ -59,7 +59,7 @@ void USFLogObject::LogHeaderRows() { ...@@ -59,7 +59,7 @@ void USFLogObject::LogHeaderRows() {
"\t" + FString("Location-Z") + "\t" + FString("Location-Z") +
"\t" + FString("Rotation-Pitch") + "\t" + FString("Rotation-Pitch") +
"\t" + FString("Rotation-Yaw") + "\t" + FString("Rotation-Yaw") +
"\t" + FString("Rotation-Yaw"); "\t" + FString("Rotation-Roll");
UniLog.Log(PositionLogHeader, "PositionLog"); UniLog.Log(PositionLogHeader, "PositionLog");
} }
......
...@@ -13,9 +13,6 @@ ...@@ -13,9 +13,6 @@
USFCondition* CurrCondition = USFGameInstance::Get()->GetParticipant()->GetCurrentCondition(); USFCondition* CurrCondition = USFGameInstance::Get()->GetParticipant()->GetCurrentCondition();
if (!CurrCondition->StoreDependentVariableData(DependentVariableName, Value)) if (!CurrCondition->StoreDependentVariableData(DependentVariableName, Value))
{ {
FSFLoggingUtils::Log(
"Cannot log data '" + Value + "' for dependent variable '" + DependentVariableName +
"' since it does not exist for this condition!", true);
return; return;
} }
LogComment("Recorded " + DependentVariableName + ": " + Value); LogComment("Recorded " + DependentVariableName + ": " + Value);
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include "UObject/UObjectGlobals.h" #include "UObject/UObjectGlobals.h"
#include "SFMapFactor.h" #include "SFMapFactor.h"
#include "Logging/SFLoggingUtils.h"
USFCondition::USFCondition() USFCondition::USFCondition()
{ {
...@@ -18,12 +19,14 @@ void USFCondition::Generate(const FString& InPhaseName, const TArray<int>& Condi ...@@ -18,12 +19,14 @@ void USFCondition::Generate(const FString& InPhaseName, const TArray<int>& Condi
for (int i = 0; i < Factors.Num(); ++i) for (int i = 0; i < Factors.Num(); ++i)
{ {
USFStudyFactor* Factor = Factors[i]; USFStudyFactor* Factor = Factors[i];
FString FactorLevel = Factor->Levels[ConditionIndices[i]];
if (Factor->IsA(USFMapFactor::StaticClass())) if (Factor->IsA(USFMapFactor::StaticClass()))
{ {
Map = Factor->Levels[ConditionIndices[i]]; Map = FactorLevel;
continue; //for better readybility strip path!
FactorLevel = FPaths::GetBaseFilename(FactorLevel);
} }
FactorLevels.Add(Factor->FactorName, Factor->Levels[ConditionIndices[i]]); FactorLevels.Add(Factor->FactorName, FactorLevel);
} }
for (USFDependentVariable* Var : DependentVars) for (USFDependentVariable* Var : DependentVars)
...@@ -86,7 +89,7 @@ FString USFCondition::CreateIdentifiableName() ...@@ -86,7 +89,7 @@ FString USFCondition::CreateIdentifiableName()
FString USFCondition::ToString() const FString USFCondition::ToString() const
{ {
FString Out = PhaseName + "_" + FPaths::GetBaseFilename(Map); FString Out = PhaseName;
for (auto Level : FactorLevels) for (auto Level : FactorLevels)
{ {
Out = Out + "_" + Level.Value; Out = Out + "_" + Level.Value;
...@@ -102,6 +105,22 @@ bool USFCondition::operator==(USFCondition& Other) ...@@ -102,6 +105,22 @@ bool USFCondition::operator==(USFCondition& Other)
bool USFCondition::StoreDependentVariableData(const FString& VarName, const FString& Value) bool USFCondition::StoreDependentVariableData(const FString& VarName, const FString& Value)
{ {
if(!WasStarted())
{
FSFLoggingUtils::Log(
"Cannot log data '" + Value + "' for dependent variable '" + VarName +
"' since condition was not started yet, probably still fading!", true);
return false;
}
if (IsFinished())
{
FSFLoggingUtils::Log(
"Cannot log data '" + Value + "' for dependent variable '" + VarName +
"' since condition was has finished, probably already fading!", true);
return false;
}
for (auto& Var : DependentVariablesValues) for (auto& Var : DependentVariablesValues)
{ {
if (Var.Key->Name == VarName) if (Var.Key->Name == VarName)
...@@ -110,6 +129,10 @@ bool USFCondition::StoreDependentVariableData(const FString& VarName, const FStr ...@@ -110,6 +129,10 @@ bool USFCondition::StoreDependentVariableData(const FString& VarName, const FStr
return true; return true;
} }
} }
FSFLoggingUtils::Log(
"Cannot log data '" + Value + "' for dependent variable '" + VarName +
"' since it does not exist for this condition!", true);
return false; return false;
} }
...@@ -151,10 +174,6 @@ bool USFCondition::RecoverStudyResults(TArray<FString>& Header, TArray<FString>& ...@@ -151,10 +174,6 @@ bool USFCondition::RecoverStudyResults(TArray<FString>& Header, TArray<FString>&
return false; return false;
} }
} }
if (!Header.Contains("Map") || Entries[Header.Find("Map")] != Map)
{
return false;
}
//so this is the right condition //so this is the right condition
for (auto& DepVar : DependentVariablesValues) for (auto& DepVar : DependentVariablesValues)
...@@ -171,7 +190,6 @@ FString USFCondition::GetPrettyName() ...@@ -171,7 +190,6 @@ FString USFCondition::GetPrettyName()
{ {
FString ConditionString = "("; FString ConditionString = "(";
ConditionString += "Phase: " + PhaseName; ConditionString += "Phase: " + PhaseName;
ConditionString += "; Map: " + FPaths::GetBaseFilename(Map);
for (auto FactorLevel : FactorLevels) for (auto FactorLevel : FactorLevels)
{ {
ConditionString += "; " + FactorLevel.Key + ": " + FactorLevel.Value; ConditionString += "; " + FactorLevel.Key + ": " + FactorLevel.Value;
......
...@@ -310,9 +310,10 @@ bool USFGameInstance::StartStudy() ...@@ -310,9 +310,10 @@ bool USFGameInstance::StartStudy()
void USFGameInstance::EndStudy() void USFGameInstance::EndStudy()
{ {
USFCondition* LastCondition = Participant->GetCurrentCondition(); USFCondition* LastCondition = Participant->GetCurrentCondition();
if (LastCondition && LastCondition->WasStarted()) if (!LastCondition || !LastCondition->WasStarted() || LastCondition->IsFinished())
LastCondition->End(); return;
LastCondition->End();
Participant->EndStudy(); Participant->EndStudy();
UpdateHUD("Study ended"); UpdateHUD("Study ended");
...@@ -327,9 +328,17 @@ void USFGameInstance::EndStudy() ...@@ -327,9 +328,17 @@ void USFGameInstance::EndStudy()
bool USFGameInstance::NextCondition(bool bForced /*=false*/) bool USFGameInstance::NextCondition(bool bForced /*=false*/)
{ {
// Check if is already fading
if (FadeHandler->GetIsFading())
{
FSFLoggingUtils::Log("[USFGameInstance::NextCondition()]: Already Fading between levels", true);
return false;
}
USFCondition* NextCondition = Participant->GetNextCondition(); USFCondition* NextCondition = Participant->GetNextCondition();
if (!NextCondition) if (!NextCondition)
{ {
FSFLoggingUtils::Log("[SFGameInstance::NextCondition]: All conditions already ran, no NextCondition", false);
EndStudy(); EndStudy();
return false; return false;
} }
...@@ -423,32 +432,30 @@ FString USFGameInstance::GetFactorLevel(FString FactorName) const ...@@ -423,32 +432,30 @@ FString USFGameInstance::GetFactorLevel(FString FactorName) const
return "FactorNotPresent"; return "FactorNotPresent";
} }
void USFGameInstance::LogToHUD(FString Text)
void USFGameInstance::UpdateHUD(FString Status)
{ {
if (GetHUD()) if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD()) &&
Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->IsWidgetPresent())
{ {
GetHUD()->UpdateHUD(Participant, Status); Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->AddLogMessage(Text);
} }
else else
{ {
HUDSavedData.Status = Status; HUDSavedData.LogMessages.Add(Text);
if (Participant)
HUDSavedData.Participant = FString::FromInt(Participant->GetID());
} }
} }
void USFGameInstance::LogToHUD(FString Text) void USFGameInstance::UpdateHUD(FString Status)
{ {
if (GetWorld()->GetFirstPlayerController() && Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD()) && if (GetHUD())
Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->IsWidgetPresent())
{ {
Cast<ASFMasterHUD>(GetWorld()->GetFirstPlayerController()->GetHUD())->AddLogMessage(Text); GetHUD()->UpdateHUD(Participant, Status);
} }
else else
{ {
HUDSavedData.LogMessages.Add(Text); HUDSavedData.Status = Status;
if (Participant)
HUDSavedData.Participant = FString::FromInt(Participant->GetID());
} }
} }
......
...@@ -18,9 +18,13 @@ USFParticipant::~USFParticipant() ...@@ -18,9 +18,13 @@ USFParticipant::~USFParticipant()
{ {
} }
bool USFParticipant::Initialize(int Participant) bool USFParticipant::Initialize(int Participant, bool bSetIdOnly /*= false*/)
{ {
ParticipantID = Participant; ParticipantID = Participant;
if(bSetIdOnly)
{
return true;
}
const FString Timestamp = FDateTime::Now().ToString(); const FString Timestamp = FDateTime::Now().ToString();
...@@ -32,6 +36,9 @@ bool USFParticipant::Initialize(int Participant) ...@@ -32,6 +36,9 @@ bool USFParticipant::Initialize(int Participant)
if (USFGameInstance::Get()) if (USFGameInstance::Get())
{ {
USFGameInstance::Get()->GetLogObject()->LogHeaderRows(); USFGameInstance::Get()->GetLogObject()->LogHeaderRows();
}
else
{
FSFLoggingUtils::Log("GameInstance not set up yet, no header rows are written to participant logs."); FSFLoggingUtils::Log("GameInstance not set up yet, no header rows are written to participant logs.");
} }
return true; return true;
...@@ -107,7 +114,6 @@ void USFParticipant::StoreInPhaseLongTable() const ...@@ -107,7 +114,6 @@ void USFParticipant::StoreInPhaseLongTable() const
if (!FPaths::FileExists(Filename)) if (!FPaths::FileExists(Filename))
{ {
FString Header = "ParticipantID"; FString Header = "ParticipantID";
Header += ",Map";
for (auto Factor : CurrCondition->FactorLevels) for (auto Factor : CurrCondition->FactorLevels)
{ {
Header += "," + Factor.Key; Header += "," + Factor.Key;
...@@ -121,7 +127,6 @@ void USFParticipant::StoreInPhaseLongTable() const ...@@ -121,7 +127,6 @@ void USFParticipant::StoreInPhaseLongTable() const
} }
FString ConditionResults = FString::FromInt(ParticipantID); FString ConditionResults = FString::FromInt(ParticipantID);
ConditionResults += "," + CurrCondition->Map;
for (auto Factor : CurrCondition->FactorLevels) for (auto Factor : CurrCondition->FactorLevels)
{ {
ConditionResults += "," + Factor.Value; ConditionResults += "," + Factor.Value;
...@@ -167,7 +172,6 @@ USFCondition* USFParticipant::GetNextCondition() const ...@@ -167,7 +172,6 @@ USFCondition* USFParticipant::GetNextCondition() const
// Get next Condition // Get next Condition
if (CurrentConditionIdx + 1 >= Conditions.Num()) if (CurrentConditionIdx + 1 >= Conditions.Num())
{ {
FSFLoggingUtils::Log("[USFParticipant::NextCondition()]: All conditions already ran, no NextCondition", false);
return nullptr; return nullptr;
} }
USFCondition* UpcomingCondition = Conditions[CurrentConditionIdx + 1]; USFCondition* UpcomingCondition = Conditions[CurrentConditionIdx + 1];
......
...@@ -120,8 +120,10 @@ bool USFStudyPhase::PhaseValid() const ...@@ -120,8 +120,10 @@ bool USFStudyPhase::PhaseValid() const
return true; return true;
} }
TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr) TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr, int PhaseIndex)
{ {
//we need the phase index, because we additionally use this to seed the Latin Square randomization so tow identical phases would have different orders
// first restructure factors, such that: // first restructure factors, such that:
// - a potential enBlock factor is the first one // - a potential enBlock factor is the first one
TArray<USFStudyFactor*> SortedFactors = SortFactors(); TArray<USFStudyFactor*> SortedFactors = SortFactors();
...@@ -194,12 +196,12 @@ TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr) ...@@ -194,12 +196,12 @@ TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr)
for (int Repetition = 0; Repetition < NrDifferentOrderRepetitions; ++Repetition) for (int Repetition = 0; Repetition < NrDifferentOrderRepetitions; ++Repetition)
{ {
const TArray<int> LatinSquareRndReOrderEnBlock = USFStudyFactor::GenerateLatinSquareOrder( const TArray<int> LatinSquareRndReOrderEnBlock = USFStudyFactor::GenerateLatinSquareOrder(
ParticipantNr + Repetition, enBlockConditions); ParticipantNr + PhaseIndex + Repetition, enBlockConditions);
for (int i = 0; i < enBlockConditions; ++i) for (int i = 0; i < enBlockConditions; ++i)
{ {
//have a different reshuffling for every enBlock block //have a different reshuffling for every enBlock block
const TArray<int> LatinSquareRndReOrder = USFStudyFactor::GenerateLatinSquareOrder( const TArray<int> LatinSquareRndReOrder = USFStudyFactor::GenerateLatinSquareOrder(
ParticipantNr + Repetition + i, NrLatinSqConditions); ParticipantNr + PhaseIndex + Repetition + i, NrLatinSqConditions);
// if we have enBlockConditions>1 we need to copy and shuffle the whole enBlock Block, otherwise the i loop is trivially run once only // if we have enBlockConditions>1 we need to copy and shuffle the whole enBlock Block, otherwise the i loop is trivially run once only
for (int j = 0; j < LatinSquareRndReOrder.Num(); ++j) for (int j = 0; j < LatinSquareRndReOrder.Num(); ++j)
...@@ -250,7 +252,7 @@ TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr) ...@@ -250,7 +252,7 @@ TArray<USFCondition*> USFStudyPhase::GenerateConditions(int ParticipantNr)
continue; continue;
} }
TArray<int> LatinSquare = USFStudyFactor::GenerateLatinSquareOrder(ParticipantNr, Factor->Levels.Num()); TArray<int> LatinSquare = USFStudyFactor::GenerateLatinSquareOrder(ParticipantNr + PhaseIndex, Factor->Levels.Num());
if (LatinSquare.Num() < ConditionsIndices.Num()) if (LatinSquare.Num() < ConditionsIndices.Num())
{ {
FSFLoggingUtils::Log( FSFLoggingUtils::Log(
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#include "Help/SFUtils.h" #include "Help/SFUtils.h"
#include "Logging/SFLogObject.h" #include "Logging/SFLogObject.h"
#include "Logging/SFLoggingUtils.h" #include "Logging/SFLoggingUtils.h"
#include "Developer/DesktopPlatform/Public/IDesktopPlatform.h"
#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h"
#include "Kismet/KismetStringLibrary.h"
ASFStudySetup::ASFStudySetup() ASFStudySetup::ASFStudySetup()
{ {
...@@ -13,6 +16,7 @@ ASFStudySetup::ASFStudySetup() ...@@ -13,6 +16,7 @@ ASFStudySetup::ASFStudySetup()
USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp")); USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp"));
RootComponent = SceneComponent; RootComponent = SceneComponent;
RootComponent->Mobility = EComponentMobility::Static; RootComponent->Mobility = EComponentMobility::Static;
JsonFile = "StudySetup.json";
#if WITH_EDITORONLY_DATA #if WITH_EDITORONLY_DATA
SpriteComponent = CreateEditorOnlyDefaultSubobject<UBillboardComponent>(TEXT("Sprite")); SpriteComponent = CreateEditorOnlyDefaultSubobject<UBillboardComponent>(TEXT("Sprite"));
...@@ -25,6 +29,51 @@ ASFStudySetup::ASFStudySetup() ...@@ -25,6 +29,51 @@ ASFStudySetup::ASFStudySetup()
#endif // WITH_EDITORONLY_DATA #endif // WITH_EDITORONLY_DATA
} }
void ASFStudySetup::PostActorCreated()
{
Super::PostActorCreated();
// PostActorCreated() is called twice when actor is drag-'n'-dropped into level because:
// First a preview actor with transient values is created when dragging out of list
// Then the final actor is created once dropped into map.
// We only want to execute the code for the latter actor -> Workaround:
if (!HasAllFlags(RF_Transient))
{
int uniqueFileExtension = 0;
int NumOfDigitsExtension;
while (FPaths::FileExists(FSFUtils::GetStudyFrameworkPath() + JsonFile))
{
NumOfDigitsExtension = FString::FromInt(uniqueFileExtension).Len();
JsonFile.RemoveFromEnd(".json");
// Filename ends with number to iterate
if (JsonFile.Right(NumOfDigitsExtension).IsNumeric())
{
uniqueFileExtension = UKismetStringLibrary::Conv_StringToInt(JsonFile.Right(NumOfDigitsExtension));
}
// Filename ends with number but with fewer digits, e.g. file9.json exists but not file10.json
else if (NumOfDigitsExtension > 1)
{
uniqueFileExtension = UKismetStringLibrary::Conv_StringToInt(JsonFile.Right(NumOfDigitsExtension - 1));
}
// There is no number at the end that should be removed before adding larger number
else
{
JsonFile = JsonFile + "1" + ".json";
continue;
}
JsonFile.RemoveFromEnd(FString::FromInt(uniqueFileExtension));
JsonFile.AppendInt(uniqueFileExtension + 1);
JsonFile.Append(".json");
FSFLoggingUtils::Log("Attempting to use " + JsonFile);
}
SaveToJson();
}
}
void ASFStudySetup::BeginPlay() void ASFStudySetup::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
...@@ -52,8 +101,8 @@ void ASFStudySetup::PreSave(const ITargetPlatform* TargetPlatform) ...@@ -52,8 +101,8 @@ void ASFStudySetup::PreSave(const ITargetPlatform* TargetPlatform)
#if WITH_EDITOR #if WITH_EDITOR
void ASFStudySetup::PostEditChangeProperty(FPropertyChangedEvent& MovieSceneBlends) void ASFStudySetup::PostEditChangeProperty(FPropertyChangedEvent& MovieSceneBlends)
{ {
//not needed anymore (done on saving map and on clicking the respective button for full control) //Re-enabled to avoid accidental data loss
//SaveToJson(); SaveToJson();
Super::PostEditChangeProperty(MovieSceneBlends); Super::PostEditChangeProperty(MovieSceneBlends);
} }
#endif #endif
...@@ -76,6 +125,20 @@ bool ASFStudySetup::CheckPhases() const ...@@ -76,6 +125,20 @@ bool ASFStudySetup::CheckPhases() const
} }
} }
for(FString PhaseName : PhasesToOrderRandomize)
{
bool bPhaseExists = false;
for(int i=0; i<Phases.Num(); ++i)
{
if (Phases[i]->PhaseName == PhaseName) bPhaseExists = true;
}
if(!bPhaseExists)
{
FSFUtils::OpenMessageBox("Phase " + PhaseName + " cannot be randomized in order, since it does not exist!", true);
return false;
}
}
return true; return true;
} }
...@@ -85,7 +148,7 @@ void ASFStudySetup::GenerateTestStudyRuns() const ...@@ -85,7 +148,7 @@ void ASFStudySetup::GenerateTestStudyRuns() const
{ {
const TArray<USFCondition*> Conditions = GetAllConditionsForRun(ParticipantID); const TArray<USFCondition*> Conditions = GetAllConditionsForRun(ParticipantID);
USFParticipant* TmpParticipant = NewObject<USFParticipant>(); USFParticipant* TmpParticipant = NewObject<USFParticipant>();
TmpParticipant->Initialize(ParticipantID); TmpParticipant->Initialize(ParticipantID, true);
TmpParticipant->SetStudyConditions(Conditions); //this also saves it to json TmpParticipant->SetStudyConditions(Conditions); //this also saves it to json
} }
} }
...@@ -124,10 +187,28 @@ TArray<USFCondition*> ASFStudySetup::GetAllConditionsForRun(int RunningParticipa ...@@ -124,10 +187,28 @@ TArray<USFCondition*> ASFStudySetup::GetAllConditionsForRun(int RunningParticipa
return TArray<USFCondition*>(); return TArray<USFCondition*>();
} }
//so we have to potentially swap some phases
TArray<int> PhasesToShuffleIndices;
for(int i=0; i<Phases.Num(); ++i)
{
if (PhasesToOrderRandomize.Contains(Phases[i]->PhaseName))
{
PhasesToShuffleIndices.Add(i);
}
}
TArray<int> LatinSquare = USFStudyFactor::GenerateLatinSquareOrder(RunningParticipantNumber, PhasesToShuffleIndices.Num());
TArray<USFCondition*> Conditions; TArray<USFCondition*> Conditions;
for (USFStudyPhase* Phase : Phases) for (int i=0; i<Phases.Num(); ++i)
{ {
Conditions.Append(Phase->GenerateConditions(RunningParticipantNumber)); int ActualIndex = i;
if(PhasesToShuffleIndices.Contains(i))
{
//this one needs to be shuffled
const int IndexInShuffleArray = PhasesToShuffleIndices.Find(i);
ActualIndex = PhasesToShuffleIndices[LatinSquare[IndexInShuffleArray]];
}
Conditions.Append(Phases[ActualIndex]->GenerateConditions(RunningParticipantNumber, ActualIndex));
} }
return Conditions; return Conditions;
} }
...@@ -158,6 +239,14 @@ TSharedPtr<FJsonObject> ASFStudySetup::GetAsJson() const ...@@ -158,6 +239,14 @@ TSharedPtr<FJsonObject> ASFStudySetup::GetAsJson() const
} }
Json->SetArrayField("Phases", PhasesArray); Json->SetArrayField("Phases", PhasesArray);
TArray<TSharedPtr<FJsonValue>> PhasesToRandomize;
for (FString Phase : PhasesToOrderRandomize)
{
TSharedRef<FJsonValueString> JsonValue = MakeShared<FJsonValueString>(Phase);
PhasesToRandomize.Add(JsonValue);
}
Json->SetArrayField("PhasesToOrderRandomize", PhasesToRandomize);
Json->SetObjectField("FadeConfig", FadeConfig.GetAsJson()); Json->SetObjectField("FadeConfig", FadeConfig.GetAsJson());
Json->SetObjectField("ExperimenterViewConfig", ExperimenterViewConfig.GetAsJson()); Json->SetObjectField("ExperimenterViewConfig", ExperimenterViewConfig.GetAsJson());
if(UseGazeTracker == EGazeTrackerMode::NotTracking) Json->SetStringField("UseGazeTracker", "NotTracking"); if(UseGazeTracker == EGazeTrackerMode::NotTracking) Json->SetStringField("UseGazeTracker", "NotTracking");
...@@ -178,20 +267,80 @@ void ASFStudySetup::FromJson(TSharedPtr<FJsonObject> Json) ...@@ -178,20 +267,80 @@ void ASFStudySetup::FromJson(TSharedPtr<FJsonObject> Json)
Phase->FromJson(PhaseJson->AsObject()); Phase->FromJson(PhaseJson->AsObject());
Phases.Add(Phase); Phases.Add(Phase);
} }
PhasesToOrderRandomize.Empty();
TArray<TSharedPtr<FJsonValue>> PhasesToRandomize = Json->GetArrayField("PhasesToOrderRandomize");
for (TSharedPtr<FJsonValue> PhaseJson : PhasesToRandomize)
{
PhasesToOrderRandomize.Add(PhaseJson->AsString());
}
FadeConfig.FromJson(Json->GetObjectField("FadeConfig")); FadeConfig.FromJson(Json->GetObjectField("FadeConfig"));
ExperimenterViewConfig.FromJson(Json->GetObjectField("ExperimenterViewConfig")); ExperimenterViewConfig.FromJson(Json->GetObjectField("ExperimenterViewConfig"));
if(Json->GetStringField("UseGazeTracker") == "NotTracking") UseGazeTracker = EGazeTrackerMode::NotTracking; if(Json->GetStringField("UseGazeTracker") == "NotTracking") UseGazeTracker = EGazeTrackerMode::NotTracking;
if(Json->GetStringField("UseGazeTracker") == "HeadRotationOnly") UseGazeTracker = EGazeTrackerMode::HeadRotationOnly; if(Json->GetStringField("UseGazeTracker") == "HeadRotationOnly") UseGazeTracker = EGazeTrackerMode::HeadRotationOnly;
if(Json->GetStringField("UseGazeTracker") == "EyeTracking") UseGazeTracker = EGazeTrackerMode::EyeTracking; if(Json->GetStringField("UseGazeTracker") == "EyeTracking") UseGazeTracker = EGazeTrackerMode::EyeTracking;
} }
void ASFStudySetup::LoadSetupFile()
{
// OpenFileDialog() requires an array for the return value,
// but the file picker window only allows one file to be selected,
// so using SelectedFilePath[0] works fine consistently
TArray<FString> SelectedFilePath;
FDesktopPlatformModule::Get()->OpenFileDialog(FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), FString("Select Setup File"), FSFUtils::GetStudyFrameworkPath(),
FString(""), FString("JSON Files|*.json"), 0, SelectedFilePath);
if (SelectedFilePath.Num() == 0 || !SelectedFilePath[0].EndsWith(".json"))
{
return;
}
// Make path relative to ProjectDir/StudyFramework
if(!FPaths::MakePathRelativeTo(SelectedFilePath[0], *FSFUtils::GetStudyFrameworkPath()))
{
FSFLoggingUtils::Log("Was not able to make selected file path relative to working directory. Ensure that the paths share the same root folder (i.e. are located on the same drive)", true);
return;
}
if (JsonFile != SelectedFilePath[0])
{
this->Modify(true);
JsonFile = SelectedFilePath[0];
}
LoadFromJson();
}
void ASFStudySetup::SaveSetupFile()
{
TArray<FString> SelectedFilePath;
FDesktopPlatformModule::Get()->SaveFileDialog(FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), FString("Save Setup File To"), FSFUtils::GetStudyFrameworkPath(),
JsonFile, FString("JSON Files|*.json"), 0, SelectedFilePath);
if (SelectedFilePath.Num() == 0 || !SelectedFilePath[0].EndsWith(".json"))
{
return;
}
// Make path relative to ProjectDir/StudyFramework
if (!FPaths::MakePathRelativeTo(SelectedFilePath[0], *FSFUtils::GetStudyFrameworkPath()))
{
FSFLoggingUtils::Log("Was not able to make selected file path relative to working directory. Ensure that the paths share the same root folder (i.e. are located on the same drive)", true);
return;
}
if (JsonFile != SelectedFilePath[0])
{
this->Modify(true);
JsonFile = SelectedFilePath[0];
}
SaveToJson();
}
void ASFStudySetup::LoadFromJson() void ASFStudySetup::LoadFromJson()
{ {
TSharedPtr<FJsonObject> Json = FSFUtils::ReadJsonFromFile(JsonFile); TSharedPtr<FJsonObject> Json = FSFUtils::ReadJsonFromFile(JsonFile);
if (Json) if (Json)
{ {
FromJson(Json); FromJson(Json);
FSFLoggingUtils::Log("Loaded setup file " + JsonFile);
} }
} }
......
...@@ -27,7 +27,6 @@ public: ...@@ -27,7 +27,6 @@ public:
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UButton* GoToButton; UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UButton* GoToButton;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Phase; UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Phase;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Map;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text0; UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text0;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text1; UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text1;
UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text2; UPROPERTY(BlueprintReadWrite, meta = (BindWidget)) UTextBlock* Text2;
......
...@@ -17,8 +17,9 @@ public: ...@@ -17,8 +17,9 @@ public:
static FString JsonToString(TSharedPtr<FJsonObject> Json); static FString JsonToString(TSharedPtr<FJsonObject> Json);
static TSharedPtr<FJsonObject> StringToJson(FString String); static TSharedPtr<FJsonObject> StringToJson(FString String);
static void WriteJsonToFile(TSharedPtr<FJsonObject> Json, FString FilenName); static void WriteJsonToFile(TSharedPtr<FJsonObject> Json, FString FilePath);
static TSharedPtr<FJsonObject> ReadJsonFromFile(FString FilenName); static TSharedPtr<FJsonObject> ReadJsonFromFile(FString FilePath);
static UWorld* GetWorld(); static UWorld* GetWorld();
static FString GetStudyFrameworkPath();
}; };
\ No newline at end of file
...@@ -53,14 +53,14 @@ public: ...@@ -53,14 +53,14 @@ public:
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
TMap<FString,FString> FactorLevels; TMap<FString,FString> FactorLevels;
UPROPERTY(BlueprintReadOnly)
FString Map = "undefined";
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
TMap<USFDependentVariable*,FString> DependentVariablesValues; TMap<USFDependentVariable*,FString> DependentVariablesValues;
protected: protected:
UPROPERTY(BlueprintReadOnly)
FString Map = "";
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
float TimeTaken=0.0; float TimeTaken=0.0;
......
...@@ -93,7 +93,7 @@ public: ...@@ -93,7 +93,7 @@ public:
// ****************************************************************** // // ****************************************************************** //
//use this to print something to the log panel of the HUD, e.g., to inform the experimenter //use this to print something to the log panel of the HUD, e.g., to inform the experimenter
UFUNCTION() UFUNCTION(BlueprintCallable)
void LogToHUD(FString Text); void LogToHUD(FString Text);
UFUNCTION() UFUNCTION()
......
...@@ -28,7 +28,7 @@ public: ...@@ -28,7 +28,7 @@ public:
USFParticipant(); USFParticipant();
~USFParticipant(); ~USFParticipant();
bool Initialize(int Participant); bool Initialize(int Participant, bool bSetIdOnly = false);
void SetStudyConditions(TArray<USFCondition*> NewConditions); void SetStudyConditions(TArray<USFCondition*> NewConditions);
bool StartStudy(); bool StartStudy();
......
...@@ -48,7 +48,7 @@ public: ...@@ -48,7 +48,7 @@ public:
bool PhaseValid() const; bool PhaseValid() const;
UFUNCTION() UFUNCTION()
TArray<USFCondition*> GenerateConditions(int ParticipantNr); TArray<USFCondition*> GenerateConditions(int ParticipantNr, int PhaseIndex);
// ****************************************************************** // // ****************************************************************** //
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
#include "HUD/SFExperimenterWindow.h" #include "HUD/SFExperimenterWindow.h"
#include "Logging/SFLogObject.h" #include "Logging/SFLogObject.h"
#include "GazeTracking/SFGazeTracker.h" #include "GazeTracking/SFGazeTracker.h"
// includes below marked as "possibly unused", but they are vital for build to succeed
#include "Components/BillboardComponent.h"
#include "Engine/Texture2D.h"
#include "SFStudySetup.generated.h" #include "SFStudySetup.generated.h"
...@@ -19,6 +22,8 @@ class STUDYFRAMEWORKPLUGIN_API ASFStudySetup : public AActor ...@@ -19,6 +22,8 @@ class STUDYFRAMEWORKPLUGIN_API ASFStudySetup : public AActor
public: public:
ASFStudySetup(); ASFStudySetup();
virtual void PostActorCreated() override;
virtual void BeginPlay() override; virtual void BeginPlay() override;
virtual void PostLoad() override; virtual void PostLoad() override;
...@@ -61,6 +66,10 @@ public: ...@@ -61,6 +66,10 @@ public:
UPROPERTY(BlueprintReadOnly, EditAnywhere, meta = (Category = "Study Setup")) UPROPERTY(BlueprintReadOnly, EditAnywhere, meta = (Category = "Study Setup"))
EGazeTrackerMode UseGazeTracker = EGazeTrackerMode::NotTracking; EGazeTrackerMode UseGazeTracker = EGazeTrackerMode::NotTracking;
//give names of phases wich should be randomized in their order between participants
UPROPERTY(BlueprintReadOnly, EditAnywhere, meta = (Category = "Study Setup"))
TArray<FString> PhasesToOrderRandomize;
// ****************************************************************** // // ****************************************************************** //
// ******* Getters ************************************************** // // ******* Getters ************************************************** //
// ****************************************************************** // // ****************************************************************** //
...@@ -74,17 +83,24 @@ public: ...@@ -74,17 +83,24 @@ public:
UFUNCTION() UFUNCTION()
USFStudyPhase* GetPhase(int Index); USFStudyPhase* GetPhase(int Index);
UPROPERTY(BlueprintReadOnly, EditAnywhere, meta = (Category = "Study Setup Json Storage")) UPROPERTY(BlueprintReadOnly,VisibleAnywhere, meta = (Category = "Study Setup Json Storage"))
FString JsonFile = "StudySetup.json"; FString JsonFile;
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Study Setup Json Storage") UFUNCTION(BlueprintCallable)
void LoadFromJson(); void LoadFromJson();
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Study Setup Json Storage")
void LoadSetupFile();
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Study Setup Json Storage")
void SaveSetupFile();
protected: protected:
TSharedPtr<FJsonObject> GetAsJson() const; TSharedPtr<FJsonObject> GetAsJson() const;
void FromJson(TSharedPtr<FJsonObject> Json); void FromJson(TSharedPtr<FJsonObject> Json);
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Study Setup Json Storage") UFUNCTION(BlueprintCallable)
void SaveToJson() const; void SaveToJson() const;
bool ContainsNullptrInArrays(); bool ContainsNullptrInArrays();
......
...@@ -58,7 +58,6 @@ public class StudyFrameworkPlugin : ModuleRules ...@@ -58,7 +58,6 @@ public class StudyFrameworkPlugin : ModuleRules
"Slate", "Slate",
"SlateCore", "SlateCore",
"UniversalLogging", "UniversalLogging",
"DisplayCluster"
// ... add private dependencies that you statically link with here ... // ... add private dependencies that you statically link with here ...
} }
); );
......