Skip to content
Snippets Groups Projects
Select Git revision
  • 7413bf66ffcc0ca80245c55d328bc3bd4dc873b9
  • 4.27 default
  • GestureModification
  • dev/5.1
4 results

MCController.cpp

  • MCController.cpp 43.35 KiB
    // Fill out your copyright notice in the Description page of Project Settings.
    
    
    #include "MCController.h"
    
    #include <string>
    
    #include "Animation/PoseAsset.h"
    
    #include "UObject/ConstructorHelpers.h"
    #include "IUniversalLogging.h"
    #include "Engine/Engine.h"
    #include "Kismet/GameplayStatics.h"
    #include "AssetRegistryModule.h"
    
    
    AMCController::AMCController() {
     	PrimaryActorTick.bCanEverTick = true;
    
    	static ConstructorHelpers::FClassFinder<AActor> WidgetObjectClass1(TEXT("/MoCapPlugin/MCInstructionWidgetBPObject"));
    	InstructionWidgetObjClass = WidgetObjectClass1.Class;
    
    	static ConstructorHelpers::FClassFinder<AActor> WidgetObjectClass2(TEXT("/MoCapPlugin/MCFeedbackWidgetBPObject"));
    	FeedbackWidgetObjClass = WidgetObjectClass2.Class;
    
    }
    
    void AMCController::EndPlay(const EEndPlayReason::Type EndPlayReason) {
    	if (IsRecording) {
    		ToggleRecording();
    	}
    }
    
    void AMCController::BeginPlay() {
    	Super::BeginPlay();
    
    	APlayerController* Controller = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    	EnableInput(Controller);
    
    	if (SetControls && InputComponent != nullptr) {
    		InputComponent->BindAction("ToggleRecording", EInputEvent::IE_Pressed, this, &AMCController::ToggleRecording);
    		InputComponent->BindAction("SetMarker", EInputEvent::IE_Pressed, this, &AMCController::SetMarker);
    		InputComponent->BindAction("StartRecording", EInputEvent::IE_Pressed, this, &AMCController::ToggleRecording);
    		InputComponent->BindAction("NextSentence", EInputEvent::IE_Pressed, this, &AMCController::SetMarker);
    		InputComponent->BindAction("SaveAnimation", EInputEvent::IE_Pressed, this, &AMCController::SaveAnimationEditor);
    	}
    
    	if (!Pawn || !Pawn->GetActorLocation().Equals(FVector(0, 0, 0)) || !Pawn->GetActorRotation().Equals(FRotator(0, 0, 0))) {
    		GEngine->AddOnScreenDebugMessage(-1, 30.0f, FColor::Red, FString::Printf(TEXT("Pawn for Recording in MCController is not positioned at 0, 0, 0 with rotation 0, 0, 0. This can lead to unwanted offsets."), *NameOfRecording));
    	}
    
    	Pawn->LoadMeasurementFile(MeasurementPath);
    	Pawn->GetAnimInstance()->PawnOwner = Pawn;
    	if (bFingerTrackingEnabled) {
    		USteamVRInputDeviceFunctionLibrary::SetCurlsAndSplaysState(true, true);
    	}
    
    	if (SpectatorCam && !UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled()) {
    		Pawn->VRCamera->SetWorldLocation(SpectatorCam->GetActorLocation());
    		Pawn->VRCamera->SetWorldRotation(SpectatorCam->GetActorRotation());
    	}
    
    	LastAddOffsets = AdditionalOffsets;
    
    }
    
    void AMCController::Tick(float DeltaTime) {
    	Super::Tick(DeltaTime);
    
    	if (!InstructionWidget) {
    		TArray<AActor*> FoundActors;
    		UGameplayStatics::GetAllActorsOfClass(GetWorld(), InstructionWidgetObjClass, FoundActors);
    		if (FoundActors.Num() > 0) {
    			InstructionWidgetObj = FoundActors[0];
    			UWidgetComponent* Component = Cast<UWidgetComponent>(FoundActors[0]->GetComponentByClass(UWidgetComponent::StaticClass()));
    			InstructionWidget = Cast<UMCInstructionWidget>(Component->GetUserWidgetObject());
    			if (!UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled()) {
    				InstructionWidget->SetVisibility(ESlateVisibility::Hidden);
    			}
    		}
    	}
    
    	if (!FeedbackWidget) {
    		TArray<AActor*> FoundActors;
    		UGameplayStatics::GetAllActorsOfClass(GetWorld(), FeedbackWidgetObjClass, FoundActors);
    		if (FoundActors.Num() > 0) {
    			UWidgetComponent* Component = Cast<UWidgetComponent>(FoundActors[0]->GetComponentByClass(UWidgetComponent::StaticClass()));
    			FeedbackWidget = Cast<UMCFeedbackWidget>(Component->GetUserWidgetObject());
    		}
    	}
    	else if(!FeedbackWidget->inited) {
    		FeedbackWidget->InitSensorBox(Pawn->SensorSetup);
    	}
    
    	if (SpectatorCam != nullptr && !SpectatorInited) {
    		USceneCaptureComponent2D* CaptureComponent = SpectatorCam->GetCaptureComponent2D();
    		CaptureComponent->HiddenActors = { InstructionWidgetObj };
    		if (CaptureComponent != nullptr && CaptureComponent->TextureTarget != nullptr) {
    			UHeadMountedDisplayFunctionLibrary::SetSpectatorScreenMode(ESpectatorScreenMode::Texture);
    			UHeadMountedDisplayFunctionLibrary::SetSpectatorScreenTexture(CaptureComponent->TextureTarget);
    			SpectatorInited = true;
    		}
    	}
    
    	if (!EditOffsetMode && IsSavingToAnim) {
    		SaveToAnimMode();
    	}
    
    	if (IsRecording) {
    		RecordMode();
    	}
    
    	LogHandler.WriteToLogFile();
    }
    
    void AMCController::RecordMode() {
    
    	TSharedPtr<FJsonObject> JsonObjectFull = MAKE_JSON;
    	JsonObjectFull->SetStringField("Type", "ViveData");
    
    	Pawn->AddSensorDataToJson(JsonObjectFull);
    	if (bFingerTrackingEnabled) {
    		Pawn->AddFingerDataToJson(JsonObjectFull, FingerTrackingMethod, FeedbackWidget);
    	}
    
    	FString OutputString = MCUtils::JsonToString(JsonObjectFull);
    	LogHandler.LogData(OutputString);
    
    	if (DebugMode) {
    		Pawn->InputViveDataToAnimInstance(JsonObjectFull);
    		if (bFingerTrackingEnabled) {
    			Pawn->InputFingerDataToAnimInstance(JsonObjectFull);
    		}
    	}
    
    }
    
    void AMCController::SaveToAnimMode() {
    
    	if (AnimSaveState.WaitForAnimInstance) {
    		if (AnimSaveState.Pawn->GetAnimInstance()->AppliedPose) {
    			AnimSaveState.WaitForAnimInstance = false;
    		}
    	}
    
    	if (!AnimSaveState.WaitForAnimInstance) {
    		if (AnimSaveState.CurrentEntryIndex < AnimSaveState.AnimData.Num()) {
    			InputNextFrame();
    		}
    		else {
    			SaveAnimSnapshots();
    			IsSavingToAnim = false;
    			//AnimSaveState.Pawn->Destroy();
    			AnimSaveState = FAnimSaveState();
    
    			if (InstructionWidget) {
    				InstructionWidget->FeedbackText->SetText(FText::FromString("Saved!"));
    				InstructionWidget->WidgetSwitcher->SetActiveWidgetIndex(InstructionWidget->StartIndex);
    			}
    			if (GEngine) {
    				GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, FString::Printf(TEXT("Animation Saved!")));
    			}
    		}
    	}
    
    }
    
    void AMCController::NextEditFrame() {
    	InputNextFrame();
    }
    
    void AMCController::PrevEditFrame() {
    	AnimSaveState.CurrentEntryIndex -= 2;
    	if (AnimSaveState.CurrentEntryIndex < 0) {
    		AnimSaveState.CurrentEntryIndex = 0;
    	}
    	InputNextFrame();
    }
    
    void AMCController::FinishEditAnim() {
    	IsSavingToAnim = false;
    	AnimSaveState = FAnimSaveState();
    }
    
    void AMCController::ScaleAnimDataInterval(int start, int end, float DeltaTime) {
    	
    	FTimespan StartTime = AnimSaveState.AnimData[start].Timestamp;
    	float OldTime = (AnimSaveState.AnimData[end].Timestamp - AnimSaveState.AnimData[start].Timestamp).GetTotalSeconds();
    	float NewTime = OldTime + DeltaTime;
    	float ScaleFactor = NewTime / OldTime;
    	int OldNrFrames = end + 1 - start;
    	int NewNrFrames = ScaleFactor * OldNrFrames;
    
    	TArray<FProcessedAnimData> OldAnimData;
    	for (int i = 0; i < AnimSaveState.AnimData.Num(); i++) {
    		OldAnimData.Push(AnimSaveState.AnimData[i]);
    	}
    
    	for (int i = end + 1; i < AnimSaveState.AnimData.Num(); i++) {
    		AnimSaveState.AnimData[i].Timestamp += FTimespan::FromSeconds(DeltaTime);
    	}
    
    	AnimSaveState.AnimData.RemoveAt(start, OldNrFrames, true);
    
    	int CurOldIndex = start;
    
    	for (int i = 0; i < NewNrFrames; i++) {
    
    		FProcessedAnimData Data;
    		Data.Timestamp = StartTime + FTimespan::FromSeconds((float)i * NewTime / (float)NewNrFrames);
    		//SmoothStop
    		float TransformedI = 1.f - FMath::Pow(1.f - ((float)i / (float)NewNrFrames), GestureHoldExcessEasingExponent);
    		FTimespan OldTimestamp = StartTime + FTimespan::FromSeconds(TransformedI * OldTime);
    
    		bool stop = false;
    		while (CurOldIndex + 1 < OldAnimData.Num() && !(OldTimestamp >= OldAnimData[CurOldIndex].Timestamp && OldTimestamp <= OldAnimData[CurOldIndex + 1].Timestamp)) {
    			CurOldIndex++;
    		}
    
    		float AlphaTime;
    		if (CurOldIndex + 1 >= OldAnimData.Num() || OldAnimData[CurOldIndex + 1].IsEnd || OldAnimData[CurOldIndex + 1].IsMarker) {
    			AlphaTime = 0.f;
    		}
    		else if (OldAnimData[CurOldIndex].IsEnd || OldAnimData[CurOldIndex].IsMarker) {
    			AlphaTime = 1.f;
    		}
    		else {
    			AlphaTime = (OldTimestamp - OldAnimData[CurOldIndex].Timestamp).GetTotalSeconds() / (OldAnimData[CurOldIndex + 1].Timestamp - OldAnimData[CurOldIndex].Timestamp).GetTotalSeconds();
    		}
    
    
    		//process sensor data
    		FSensorData& SensorData = Data.SensorData;
    		for (int j = 0; j < EBodyPart::LAST; j++) {
    
    			EBodyPart Type = EBodyPart(j);
    			FSensorDataEntry* Entry = SensorData.GetEntry(Type);
    
    			const FSensorDataEntry* OldEntry0 = OldAnimData[CurOldIndex].SensorData.GetEntry(Type);
    			if (AlphaTime > 0.f) {
    				
    				const FSensorDataEntry* OldEntry1 = OldAnimData[CurOldIndex + 1].SensorData.GetEntry(Type);
    
    				Entry->Pos = (1.f - AlphaTime) * OldEntry0->Pos + AlphaTime * OldEntry1->Pos;
    				Entry->Rot = FQuat::Slerp(OldEntry0->Rot, OldEntry1->Rot, AlphaTime);
    				Entry->valid = true;
    				Entry->PosUsed = true;
    
    			}
    			else {
    				Entry->Pos = OldEntry0->Pos;
    				Entry->Rot = OldEntry0->Rot;
    				Entry->valid = true;
    				Entry->PosUsed = true;
    			}
    		}
    
    		//process finger data
    		if (bFingerTrackingEnabled) {
    
    			FFingerData& FingerData = Data.FingerData;
    			for (int f = 0; f < 2; f++) {
    				
    				FFingerDataEntry* Hand = f == 0 ? &FingerData.LeftHand : &FingerData.RightHand;
    				FFingerDataEntry* OldHand0 = f == 0 ? &OldAnimData[CurOldIndex].FingerData.LeftHand : &OldAnimData[CurOldIndex].FingerData.RightHand;
    				FFingerDataEntry* OldHand1 = AlphaTime > 0.f ? (f == 0 ? &OldAnimData[CurOldIndex + 1].FingerData.LeftHand : &OldAnimData[CurOldIndex + 1].FingerData.RightHand) : (&OldAnimData[CurOldIndex].FingerData.RightHand);
    
    				Hand->Thumb = (1.f - AlphaTime) * OldHand0->Thumb + AlphaTime * OldHand1->Thumb;
    				Hand->Index = (1.f - AlphaTime) * OldHand0->Index + AlphaTime * OldHand1->Index;
    				Hand->Middle = (1.f - AlphaTime) * OldHand0->Middle + AlphaTime * OldHand1->Middle;
    				Hand->Ring = (1.f - AlphaTime) * OldHand0->Ring + AlphaTime * OldHand1->Ring;
    				Hand->Pinky = (1.f - AlphaTime) * OldHand0->Pinky + AlphaTime * OldHand1->Pinky;
    				Hand->Thumb_Index = (1.f - AlphaTime) * OldHand0->Thumb_Index + AlphaTime * OldHand1->Thumb_Index;
    				Hand->Index_Middle = (1.f - AlphaTime) * OldHand0->Index_Middle + AlphaTime * OldHand1->Index_Middle;
    				Hand->Middle_Ring = (1.f - AlphaTime) * OldHand0->Middle_Ring + AlphaTime * OldHand1->Middle_Ring;
    				Hand->Ring_Pinky = (1.f - AlphaTime) * OldHand0->Ring_Pinky + AlphaTime * OldHand1->Ring_Pinky;
    
    			}
    
    		}
    
    		AnimSaveState.AnimData.Insert(Data, start + i);
    
    	}
    
    }
    
    bool AMCController::PreprocessRecording(const TArray<float>& HaltingPoints) {
    
    	UMCAnimInstance* AI = AnimSaveState.Pawn->GetAnimInstance();
    
    	//----translate json data to AnimSaveState.AnimData----
    
    	FTimespan LastStamp;
    	FTimespan NextFrame;
    	int CurrentMarker = 0;
    	float MaxPelvisSpineLength = -1.f;
    
    	for (int i = 0; i < AnimSaveState.StringData.Num(); i++) {
    
    		auto Entry = AnimSaveState.StringData[i];
    
    		FString TimeString, JsonString;
    		Entry.Split(" ", &TimeString, &JsonString);
    		FTimespan Timestamp = MCUtils::StringToTimespan(TimeString);
    
    		if (Timestamp < LastStamp) {
    			Timestamp = Timestamp + FTimespan::FromHours(1);
    		}
    
    		TSharedPtr<FJsonObject> TmpJson = MCUtils::StringToJson(JsonString);
    
    		if (!TmpJson->HasField("Type")) {
    			continue;
    		}
    
    		FString Type = TmpJson->GetStringField("Type");
    
    		if (Type == "Start") {
    			LastStamp = Timestamp;
    			NextFrame = Timestamp;
    		}
    		else if (Type == "Marker" || Type == "End") {
    
    			LastStamp = Timestamp;
    			NextFrame = Timestamp;
    
    			AnimSaveState.AnimData.Push(FProcessedAnimData());
    			AnimSaveState.AnimData.Last().Timestamp = Timestamp;
    			if (Type == "Marker") {
    				AnimSaveState.AnimData.Last().IsMarker = true;
    				CurrentMarker++;
    			}
    			else {
    				AnimSaveState.AnimData.Last().IsEnd = true;
    			}
    
    		}
    		else if (Type == "ViveData") {
    
    			AnimSaveState.Pawn->InputViveDataToAnimInstance(TmpJson);
    			if (bFingerTrackingEnabled) {
    				AnimSaveState.Pawn->InputFingerDataToAnimInstance(TmpJson);
    			}
    
    			if (CurrentMarker > 0 && Timestamp > NextFrame) {
    
    				AnimSaveState.AnimData.Push(FProcessedAnimData());
    				AnimSaveState.AnimData.Last().Timestamp = Timestamp;
    				AnimSaveState.AnimData.Last().SensorData = AI->SensorData;
    				if (bFingerTrackingEnabled) {
    					AnimSaveState.AnimData.Last().FingerData = AI->FingerData;
    				}
    
    				NextFrame = NextFrame + FTimespan::FromSeconds(AnimSaveState.SPF);
    
    			}
    
    		}
    		else if (Type == "Offsets") {
    			AnimSaveState.Pawn->InputViveOffsetsToAnimInstance(TmpJson);
    		}
    
    	}
    
    	//If the animation is empty, continue
    	if (AnimSaveState.AnimData.Num() <= 1) {
    		return false;
    	}
    
    	//----process the anim data----
    
    	//---Sensor turned off -> interpolate
    	for (int i = 0; i < AnimSaveState.AnimData.Num(); i++) {
    
    		FProcessedAnimData& AnimData = AnimSaveState.AnimData[i];
    
    		if (AnimData.IsMarker || AnimData.IsEnd) {
    			continue;
    		}
    
    		//process sensor data
    		FSensorData& SensorData = AnimData.SensorData;
    		for (int j = 0; j < EBodyPart::LAST; j++) {
    
    			EBodyPart Type = EBodyPart(j);
    			FSensorDataEntry* Entry = SensorData.GetEntry(Type);
    
    			if (Entry->Pos.IsNearlyZero() && Entry->Rot.Rotator().IsNearlyZero()) {
    
    				FVector StartPos = Entry->Pos;
    				FQuat StartRot = Entry->Rot;
    				int StartIndex = i;
    				if (i > 0) {
    					StartPos = AnimSaveState.AnimData[i - 1].SensorData.GetEntry(Type)->Pos;
    					StartRot = AnimSaveState.AnimData[i - 1].SensorData.GetEntry(Type)->Rot;
    					StartIndex = i - 1;
    				}
    
    				FVector EndPos;
    				FQuat EndRot;
    				int EndIndex = AnimSaveState.AnimData.Num() - 1;
    				for (int k = i + 1; k < AnimSaveState.AnimData.Num(); k++) {
    					FSensorDataEntry* CurEntry = AnimSaveState.AnimData[k].SensorData.GetEntry(Type);
    					if (!CurEntry->Pos.IsNearlyZero() || !CurEntry->Rot.Rotator().IsNearlyZero()) {
    						EndPos = CurEntry->Pos;
    						EndRot = CurEntry->Rot;
    						EndIndex = k;
    						break;
    					}
    				}
    
    				float alphaStep = 1.f / (float)(EndIndex - StartIndex);
    				float alpha = alphaStep;
    				for (int k = StartIndex + 1; k < EndIndex; k++) {
    
    					FSensorDataEntry* CurEntry = AnimSaveState.AnimData[k].SensorData.GetEntry(Type);
    					CurEntry->Pos = (1.f - alpha) * StartPos + alpha * EndPos;
    					CurEntry->Rot = FQuat::Slerp(StartRot, EndRot, alpha);
    
    					//process finger data
    					if ((Type == EBodyPart::HandL || Type == EBodyPart::HandR) && bFingerTrackingEnabled) {
    
    						FFingerData& FingerData = AnimData.FingerData;
    						for (int f = 0; f < 2; f++) {
    
    							FFingerDataEntry* Hand = f == 0 ? &FingerData.LeftHand : &FingerData.RightHand;
    							FFingerDataEntry* HandStart = f == 0 ? &AnimSaveState.AnimData[StartIndex].FingerData.LeftHand : &AnimSaveState.AnimData[StartIndex].FingerData.RightHand;
    							FFingerDataEntry* HandEnd = f == 0 ? &AnimSaveState.AnimData[EndIndex].FingerData.LeftHand : &AnimSaveState.AnimData[EndIndex].FingerData.RightHand;
    
    							Hand->Thumb = (1.f - alpha) * HandStart->Thumb + alpha * HandEnd->Thumb;
    							Hand->Index = (1.f - alpha) * HandStart->Index + alpha * HandEnd->Index;
    							Hand->Middle = (1.f - alpha) * HandStart->Middle + alpha * HandEnd->Middle;
    							Hand->Ring = (1.f - alpha) * HandStart->Ring + alpha * HandEnd->Ring;
    							Hand->Pinky = (1.f - alpha) * HandStart->Pinky + alpha * HandEnd->Pinky;
    							Hand->Thumb_Index = (1.f - alpha) * HandStart->Thumb_Index + alpha * HandEnd->Thumb_Index;
    							Hand->Index_Middle = (1.f - alpha) * HandStart->Index_Middle + alpha * HandEnd->Index_Middle;
    							Hand->Middle_Ring = (1.f - alpha) * HandStart->Middle_Ring + alpha * HandEnd->Middle_Ring;
    							Hand->Ring_Pinky = (1.f - alpha) * HandStart->Ring_Pinky + alpha * HandEnd->Ring_Pinky;
    
    						}
    
    					}
    
    					alpha += alphaStep;
    
    				}
    
    				
    
    			}
    
    		}
    
    	}
    
    	//---Smoothing of legs---
    	int smoothTimes = LegSmoothTimes;
    	for (int l = 0; l < smoothTimes; l++) {
    
    		TArray<FProcessedAnimData> OldAnimData = AnimSaveState.AnimData;
    
    		for (int i = 0; i < AnimSaveState.AnimData.Num(); i++) {
    
    			FProcessedAnimData& AnimData = AnimSaveState.AnimData[i];
    			if (AnimData.IsMarker || AnimData.IsEnd) {
    				continue;
    			}
    
    			TArray<EBodyPart> BodyParts = { EBodyPart::LowerBody, EBodyPart::FootL, EBodyPart::FootR };
    			for (EBodyPart BodyPart : BodyParts) {
    
    				FVector PosRes;
    				FQuat RotRes = OldAnimData[i].SensorData.GetEntry(BodyPart)->Rot;
    				int RotNum = 1;
    
    				int window = LegSmoothWindow;
    				int sum = 2 * window + 1;
    
    				FVector LastPos = OldAnimData[i].SensorData.GetEntry(BodyPart)->Pos;
    				FQuat LastRot = OldAnimData[i].SensorData.GetEntry(BodyPart)->Rot;
    				for (int k = 1; k < window; k++) {
    					int j = i - k;
    					if (j < 0 || OldAnimData[j].IsMarker) {
    						PosRes += ((window - k) / (float)sum) * LastPos;
    						RotRes = FQuat::Slerp(RotRes, LastRot, (float)RotNum / (float)(RotNum + (window - k)));
    						break;
    					}
    					else {
    						LastPos = OldAnimData[j].SensorData.GetEntry(BodyPart)->Pos;
    						PosRes += LastPos / (float)sum;
    						LastRot = OldAnimData[j].SensorData.GetEntry(BodyPart)->Rot;
    						RotRes = FQuat::Slerp(RotRes, LastRot, (float)RotNum / (RotNum + 1.f));
    						RotNum++;
    					}
    				}
    
    				LastPos = OldAnimData[i].SensorData.GetEntry(BodyPart)->Pos;
    				LastRot = OldAnimData[i].SensorData.GetEntry(BodyPart)->Rot;
    				for (int k = 0; k < window; k++) {
    					int j = i + k;
    					if (j >= OldAnimData.Num() || OldAnimData[j].IsMarker || OldAnimData[j].IsEnd) {
    						PosRes += ((window - k) / (float)sum) * LastPos;
    						RotRes = FQuat::Slerp(RotRes, LastRot, (float)RotNum / (float)(RotNum + (window - k)));
    						break;
    					}
    					else {
    						LastPos = OldAnimData[j].SensorData.GetEntry(BodyPart)->Pos;
    						PosRes += LastPos / (float)sum;
    						LastRot = OldAnimData[j].SensorData.GetEntry(BodyPart)->Rot;
    						RotRes = FQuat::Slerp(RotRes, LastRot, (float)RotNum / (RotNum + 1.f));
    						RotNum++;
    					}
    				}
    
    				if (BodyPart != EBodyPart::LowerBody) {
    					AnimSaveState.AnimData[i].SensorData.GetEntry(BodyPart)->Pos = PosRes;
    				}
    				AnimSaveState.AnimData[i].SensorData.GetEntry(BodyPart)->Rot = RotRes;
    
    			}
    
    		}
    	}
    
    	//---find last slow part of the gesture and scale it up---
    	
    	TArray<float> GestureSpeed;
    	bool newMethod = true;
    	
    	if (!newMethod) {
    		if (GestureHoldExcessTime > 0.f) {
    
    			for (int i = 1; i < AnimSaveState.AnimData.Num(); i++) {
    
    				FProcessedAnimData& AnimData = AnimSaveState.AnimData[i];
    
    				if (AnimData.IsMarker || AnimData.IsEnd) {
    					continue;
    				}
    
    				//process sensor data
    
    				float Speed = 0.f;
    				int SpeedCount = 0;
    				int window = 1;
    
    				FSensorData& SensorData = AnimData.SensorData;
    				for (int j = 0; j < EBodyPart::LAST; j++) {
    
    					EBodyPart Type = EBodyPart(j);
    					FSensorDataEntry* Entry = SensorData.GetEntry(Type);
    
    					if (Type == EBodyPart::HandL || Type == EBodyPart::HandR) {
    
    						for (int w = 0; w < window; w++) {
    							int index = i + w;
    							if (index - 1 >= 0 && index < AnimSaveState.AnimData.Num()) {
    								FProcessedAnimData& Data0 = AnimSaveState.AnimData[index - 1];
    								FProcessedAnimData& Data1 = AnimSaveState.AnimData[index];
    								float Time = (Data1.Timestamp - Data0.Timestamp).GetTotalSeconds();
    								Speed += Time * (Data1.SensorData.GetEntry(Type)->Pos - Data0.SensorData.GetEntry(Type)->Pos).Size();
    
    							}
    						}
    
    						SpeedCount++;
    
    					}
    
    				}
    
    				GestureSpeed.Push(Speed / (float)SpeedCount);
    
    			}
    
    			//smooth
    			TArray<float> SmoothedGestureSpeed;
    			int window = 5;
    			int Num = GestureSpeed.Num();
    			for (int i = 0; i < Num; i++) {
    				float avg = 0.f;
    				for (int wInd = -window; wInd <= window; wInd++) {
    					int w = i + wInd;
    					if (w < 0) {
    						avg += (GestureSpeed[0] + GestureSpeed[1] + GestureSpeed[2] + GestureSpeed[3] + GestureSpeed[4]) / 5.f;
    					}
    					else if (w >= Num) {
    						avg += (GestureSpeed[Num - 1] + GestureSpeed[Num - 2] + GestureSpeed[Num - 3] + GestureSpeed[Num - 4] + GestureSpeed[Num - 5]) / 5.f;
    					}
    					else {
    						avg += GestureSpeed[w];
    					}
    				}
    				avg /= (float)(window * 2 + 1);
    				SmoothedGestureSpeed.Push(avg);
    			}
    
    			GestureSpeed = SmoothedGestureSpeed;
    
    			int MaxNumToScale = 30;
    
    			//maximum
    			int maxWindow = 8;
    			int ScaleStart;
    			for (ScaleStart = GestureSpeed.Num() - 1; ScaleStart >= 0 && ScaleStart >= GestureSpeed.Num() - MaxNumToScale; ScaleStart--) {
    				bool isMax = true;
    				for (int w = -maxWindow; w <= maxWindow; w++) {
    					int index = ScaleStart + w;
    					if (index >= 0 && index < GestureSpeed.Num() && index != ScaleStart) {
    						if (GestureSpeed[index] >= GestureSpeed[ScaleStart]) {
    							isMax = false;
    							break;
    						}
    					}
    				}
    				if (isMax) {
    					break;
    				}
    			}
    
    			ScaleAnimDataInterval(ScaleStart, AnimSaveState.AnimData.Num() - 2, GestureHoldExcessTime);
    
    		}
    	}
    	else {
    		if (HaltingPoints.Num() > 0) {
    
    			FTimespan Start;
    			bool StartTimeInitialized = false;
    			bool hold = false;
    			int holdStartingIndex = -1;
    
    			for (int i = 1; i < AnimSaveState.AnimData.Num(); i++) {
    
    				FProcessedAnimData& AnimData = AnimSaveState.AnimData[i];
    
    				if (AnimData.IsMarker || AnimData.IsEnd) {
    					continue;
    				}
    
    				if (!StartTimeInitialized) {
    					StartTimeInitialized = true;
    					Start = AnimData.Timestamp;
    				}
    
    				//process sensor data
    
    				FSensorData& SensorData = AnimData.SensorData;
    				for (int j = 0; j < EBodyPart::LAST; j++) {
    
    					EBodyPart Type = EBodyPart(j);
    					FSensorDataEntry* Entry = SensorData.GetEntry(Type);
    
    					if (Type == EBodyPart::HandL || Type == EBodyPart::HandR
    						|| Type == EBodyPart::LowerArmL || Type == EBodyPart::LowerArmR) {
    
    						if (DoHolding && hold) {
    							if (Type == EBodyPart::HandL || Type == EBodyPart::HandR) {
    								Entry->Pos = AnimSaveState.AnimData[i - 1].SensorData.GetEntry(Type)->Pos;
    							}
    							Entry->Rot = AnimSaveState.AnimData[i - 1].SensorData.GetEntry(Type)->Rot;
    						}
    
    					}
    
    				}
    
    				//or time unit?
    				if (!hold && HaltingPoints[0] <= (AnimData.Timestamp - Start).GetTotalSeconds()) {
    					hold = true;
    					holdStartingIndex = i;
    				}
    
    			}
    
    			if (hold && GestureHoldExcessTime > 0.f) {
    				ScaleAnimDataInterval(holdStartingIndex, AnimSaveState.AnimData.Num() - 2, GestureHoldExcessTime);
    			}
    
    		}
    	}
    
    	//---adjust feet position so that they are located at the green foot indicators---
    
    	int amount = 0;
    	int start = 0;
    
    	for (int i = 0; i < AnimSaveState.AnimData.Num() && LockFeet && LeftFootPlane && RightFootPlane; i++) {
    
    		FProcessedAnimData& AnimData = AnimSaveState.AnimData[i];
    
    		if (AnimData.IsMarker || AnimData.IsEnd) {
    			
    			FVector cog(0, 0, 0);
    			for (int j = start; j < i; j++) {
    				FSensorData& SensorData = AnimSaveState.AnimData[j].SensorData;
    				FVector LPos = SensorData.GetEntry(EBodyPart::LowerLegL)->Pos;
    				FQuat LRot = SensorData.GetEntry(EBodyPart::LowerLegL)->Rot;
    				Pawn->OffsetSensorData(EBodyPart::LowerLegL, LPos, LRot);
    				FVector RPos = SensorData.GetEntry(EBodyPart::LowerLegR)->Pos;
    				FQuat RRot = SensorData.GetEntry(EBodyPart::LowerLegR)->Rot;
    				Pawn->OffsetSensorData(EBodyPart::LowerLegR, RPos, RRot);
    				cog += 0.5f * (LPos + RPos) * (1.f / amount);
    			}
    
    			FVector shiftAmount = 0.5f * (LeftFootPlane->GetActorLocation() + RightFootPlane->GetActorLocation()) - cog - FVector(0, 10, 0);
    			shiftAmount.Z = 0;
    			for (int j = start; j < i; j++) {
    				FSensorData& SensorData = AnimSaveState.AnimData[j].SensorData;
    				for (int k = 0; k < EBodyPart::LAST; k++) {
    					EBodyPart Type = EBodyPart(k);
    					FSensorDataEntry* Entry = SensorData.GetEntry(Type);
    					Entry->Pos += shiftAmount;
    				}
    			}
    
    			for (int j = start; j < i; j++) {
    				FSensorData& SensorData = AnimSaveState.AnimData[j].SensorData;
    				FVector LPos = SensorData.GetEntry(EBodyPart::LowerLegL)->Pos;
    				FQuat LRot = SensorData.GetEntry(EBodyPart::LowerLegL)->Rot;
    				Pawn->OffsetSensorData(EBodyPart::LowerLegL, LPos, LRot);
    				FVector RPos = SensorData.GetEntry(EBodyPart::LowerLegR)->Pos;
    				FQuat RRot = SensorData.GetEntry(EBodyPart::LowerLegR)->Rot;
    				Pawn->OffsetSensorData(EBodyPart::LowerLegR, RPos, RRot);
    
    				shiftAmount = LeftFootPlane->GetActorLocation() - LPos - FVector(-4, 10, 0);
    				SensorData.GetEntry(EBodyPart::LowerLegL)->Pos.X += shiftAmount.X;
    				SensorData.GetEntry(EBodyPart::LowerLegL)->Pos.Y += shiftAmount.Y;
    				shiftAmount = RightFootPlane->GetActorLocation() - RPos - FVector(4, 10, 0);
    				SensorData.GetEntry(EBodyPart::LowerLegR)->Pos.X += shiftAmount.X;
    				SensorData.GetEntry(EBodyPart::LowerLegR)->Pos.Y += shiftAmount.Y;
    			}
    
    
    			amount = 0;
    			start = i + 1;
    			continue;
    		}
    
    		amount++;
    	
    	}
    
    	AnimSaveState.NextFrame = FTimespan();
    	
    	
    	//debugging things
    	
    	std::string speed = "";
    
    	for (int i = 0; i < GestureSpeed.Num(); i++) {
    		speed += "\n" + std::to_string(GestureSpeed[i]);
    	}
    
    	return true;
    
    }
    
    
    void AMCController::InputNextFrame() {
    
    	bool stop = false;
    	UMCAnimInstance* AI = AnimSaveState.Pawn->GetAnimInstance();
    
    	for (; !stop && AnimSaveState.CurrentEntryIndex < AnimSaveState.AnimData.Num();) {
    
    		auto& Entry = AnimSaveState.AnimData[AnimSaveState.CurrentEntryIndex];
    		AnimSaveState.CurrentEntryIndex++;
    
    		if (Entry.IsMarker || Entry.IsEnd) {
    
    			if (AnimSaveState.CurrentMarker != 0) {
    				AI->SnapshotAnimations.Last().Fps = AnimSaveState.FPS;
    				AI->SnapshotAnimations.Last().EndTime = Entry.Timestamp;
    			}
    
    			if (Entry.IsMarker) {
    				AI->SnapshotAnimations.Add(FSnapshotAnimations());
    				AI->SnapshotAnimations.Last().StartTime = Entry.Timestamp;
    				AnimSaveState.CurrentMarker++;
    
    				if (FeedbackWidget) {
    					FeedbackWidget->RecordingStateText->SetText(FText::FromString("Converting Anim " + FString::FromInt(AnimSaveState.CurrentMarker) + "..."));
    				}
    			}
    			else { // if (Type == "End")
    				AnimSaveState.CurrentEntryIndex++;
    				stop = true;
    			}
    		}
    
    		else {
    
    			AI->SensorData = Entry.SensorData;
    			if (bFingerTrackingEnabled) {
    				AI->FingerData = Entry.FingerData;
    			}
    
    			AI->SnapshotAnimations.Last().TimeStamps.Push(Entry.Timestamp);
    
    			AnimSaveState.Pawn->AICalcFrame();
    
    			stop = true;
    			if (!EditOffsetMode) {
    				AnimSaveState.WaitForAnimInstance = true;
    			}
    
    			if (FeedbackWidget) {
    				FeedbackWidget->ProgBar->SetPercent((float)AnimSaveState.CurrentEntryIndex / (float)AnimSaveState.AnimData.Num());
    			}
    		}
    		
    
    	}
    
    
    }
    
    void AMCController::SaveAnimSnapshots() {
    
    	if (FeedbackWidget) {
    		FeedbackWidget->RecordingStateText->SetText(FText::FromString("Saving anim data to asset files..."));
    	}
    	const TArray<FSnapshotAnimations>& Anims = AnimSaveState.Pawn->GetAnimInstance()->SnapshotAnimations;
    
    	FString FolderName;
    	int j = 0;
    	do {
    		if (j == 0) {
    			FolderName = NameOfRecording;
    		}
    		else if (j < 10) {
    			FolderName = NameOfRecording + "_0" + FString::FromInt(j);
    		}
    		else {
    			FolderName = NameOfRecording + "_" + FString::FromInt(j);
    		}
    		j++;
    	} while (FPaths::DirectoryExists(FPaths::ProjectContentDir() + "/" + FolderName));
    
    	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
    	PlatformFile.CreateDirectory(*(FPaths::ProjectContentDir() + "/" + FolderName));
    
    	for (int i = 0; i < Anims.Num(); i++) {
    		FString AnimName;
    		if (i < 10) {
    			AnimName = NameOfRecording + "_0" + FString::FromInt(i + 1);
    		}
    		else {
    			AnimName = NameOfRecording + "_" + FString::FromInt(i + 1);
    		}
    
    		UAnimSequence* AnimSequence = SaveAsAnimSequence(Anims[i], AnimName, FolderName);
    
    		AnimSaveState.AnimSequences.Add(AnimSequence);
    	}
    
    	if (FeedbackWidget) {
    		FeedbackWidget->RecordingStateText->SetText(FText::FromString("Done!"));
    		FeedbackWidget->ProgBar->SetPercent(1);
    	}
    
    }
    
    UAnimSequence* AMCController::SaveAsAnimSequence(const FSnapshotAnimations& Recording, const FString& AnimName, const FString& FolderName) {
    #if WITH_EDITOR
    	const FString AnimationRelative = FPaths::Combine(TEXT("/Game"), FolderName + "/" + AnimName);
    
    	const FString AnimationAbsolute = FPaths::Combine(FPaths::ProjectContentDir(), FolderName + "/" + AnimName);
    
    	UAnimSequence* AnimationSequence = nullptr;
    
    	UPackage* Package = LoadPackage(nullptr, *AnimationAbsolute, LOAD_None);
    
    	if (Package != nullptr) {
    		AnimationSequence = FindObject<UAnimSequence>(Package, *AnimName);
    	}
    	else {
    		Package = CreatePackage(*AnimationRelative);
    	}
    	if (AnimationSequence == nullptr) {
    		AnimationSequence = NewObject<UAnimSequence>(Package, UAnimSequence::StaticClass(), *AnimName, RF_Public | RF_Standalone);
    		if (AnimationSequence) {
    			
    			AnimationSequence->SetSkeleton(AnimSaveState.Pawn->GetSkeleton());
    
    			int32 RefPelvisIndex = AnimSaveState.Pawn->SkeletalMesh->SkeletalMesh->RefSkeleton.FindBoneIndex("pelvis");
    			int32 PelvisIndex = AnimationSequence->GetSkeleton()->GetSkeletonBoneIndexFromMeshBoneIndex(AnimSaveState.Pawn->SkeletalMesh->SkeletalMesh, RefPelvisIndex);
    			AnimationSequence->GetSkeleton()->SetBoneTranslationRetargetingMode(PelvisIndex, EBoneTranslationRetargetingMode::Animation, false);
    
    			AnimationSequence->SetRawNumberOfFrame(Recording.Snapshots.Num());
    			AnimationSequence->SequenceLength = (Recording.EndTime - Recording.StartTime).GetTotalSeconds();
    
    			SetBonesAnimationInAnimSeq(Recording, AnimationSequence);
    
    			FAssetRegistryModule::AssetCreated(AnimationSequence);
    			const FString Filename = FString::Printf(TEXT("%s%s"), *AnimationAbsolute, *FPackageName::GetAssetPackageExtension());
    			UPackage::SavePackage(Package, nullptr, RF_Public | RF_Standalone, *Filename);
    		}
    	}
    
    	return AnimationSequence;
    #else
    	UE_LOG(LogTemp, Error, TEXT("VHLipSync::SaveAsAnimSequence can only be used in Editor builds!"));
    	return nullptr;
    #endif
    
    }
    
    void AMCController::SetBonesAnimationInAnimSeq(const FSnapshotAnimations& Recording, UAnimSequence* AnimSequence) {
    #if WITH_EDITOR
    
    	TArray<FName> BoneNames;
    	for (int i = 0; i < Recording.Snapshots.Num(); i++) {
    		for (int j = 0; j < Recording.Snapshots[i].BoneNames.Num(); j++) {
    			if (!BoneNames.Contains(Recording.Snapshots[i].BoneNames[j])) {
    				BoneNames.Add(Recording.Snapshots[i].BoneNames[j]);
    			}
    		}
    	}
    
    	for (const FName& BoneName : BoneNames) {
    
    		FRawAnimSequenceTrack Track;
    
    		for (int Frame = 0; Frame < Recording.Snapshots.Num(); Frame++) {
    
    			const FPoseSnapshot& Snapshot = Recording.Snapshots[Frame];
    			int32 Index = Snapshot.BoneNames.IndexOfByKey(BoneName);
    
    			if (Index != INDEX_NONE) {
    				Track.RotKeys.Add(Snapshot.LocalTransforms[Index].GetRotation());
    				Track.PosKeys.Add(Snapshot.LocalTransforms[Index].GetLocation());
    			}
    			else if (Frame == 0) {
    				Track.RotKeys.Add(FRotator::ZeroRotator.Quaternion());
    				Track.PosKeys.Add(FVector::ZeroVector);
    			}
    			else {
    				Track.RotKeys.Add(Track.RotKeys[Frame - 1]);
    				Track.PosKeys.Add(Track.PosKeys[Frame - 1]);
    			}
    
    		}
    
    		AnimSequence->AddNewRawTrack(BoneName, &Track);
    	}
    
    #endif
    
    }
    
    #if WITH_EDITOR
    void AMCController::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) {
    	Super::PostEditChangeProperty(PropertyChangedEvent);
    
    	FProperty* prop = PropertyChangedEvent.Property;
    	FFloatProperty* fprop = CastField<FFloatProperty>(prop);
    	if (fprop != nullptr) {
    
    		float* val = nullptr;
    		{
    			if (LastAddOffsets.HeadRotOffset.Roll != AdditionalOffsets.HeadRotOffset.Roll) {
    				val = &AdditionalOffsets.HeadRotOffset.Roll;
    			}
    			if (LastAddOffsets.HeadRotOffset.Yaw != AdditionalOffsets.HeadRotOffset.Yaw) {
    				val = &AdditionalOffsets.HeadRotOffset.Yaw;
    			}
    			if (LastAddOffsets.HeadRotOffset.Pitch != AdditionalOffsets.HeadRotOffset.Pitch) {
    				val = &AdditionalOffsets.HeadRotOffset.Pitch;
    			}
    			if (LastAddOffsets.HeadPosOffset.X != AdditionalOffsets.HeadPosOffset.X) {
    				val = &AdditionalOffsets.HeadPosOffset.X;
    			}
    			if (LastAddOffsets.HeadPosOffset.Y != AdditionalOffsets.HeadPosOffset.Y) {
    				val = &AdditionalOffsets.HeadPosOffset.Y;
    			}
    			if (LastAddOffsets.HeadPosOffset.Z != AdditionalOffsets.HeadPosOffset.Z) {
    				val = &AdditionalOffsets.HeadPosOffset.Z;
    			}
    
    			if (LastAddOffsets.HandLRotOffset.Roll != AdditionalOffsets.HandLRotOffset.Roll) {
    				val = &AdditionalOffsets.HandLRotOffset.Roll;
    			}
    			if (LastAddOffsets.HandLRotOffset.Yaw != AdditionalOffsets.HandLRotOffset.Yaw) {
    				val = &AdditionalOffsets.HandLRotOffset.Yaw;
    			}
    			if (LastAddOffsets.HandLRotOffset.Pitch != AdditionalOffsets.HandLRotOffset.Pitch) {
    				val = &AdditionalOffsets.HandLRotOffset.Pitch;
    			}
    			if (LastAddOffsets.HandLPosOffset.X != AdditionalOffsets.HandLPosOffset.X) {
    				val = &AdditionalOffsets.HandLPosOffset.X;
    			}
    			if (LastAddOffsets.HandLPosOffset.Y != AdditionalOffsets.HandLPosOffset.Y) {
    				val = &AdditionalOffsets.HandLPosOffset.Y;
    			}
    			if (LastAddOffsets.HandLPosOffset.Z != AdditionalOffsets.HandLPosOffset.Z) {
    				val = &AdditionalOffsets.HandLPosOffset.Z;
    			}
    
    			if (LastAddOffsets.HandRRotOffset.Roll != AdditionalOffsets.HandRRotOffset.Roll) {
    				val = &AdditionalOffsets.HandRRotOffset.Roll;
    			}
    			if (LastAddOffsets.HandRRotOffset.Yaw != AdditionalOffsets.HandRRotOffset.Yaw) {
    				val = &AdditionalOffsets.HandRRotOffset.Yaw;
    			}
    			if (LastAddOffsets.HandRRotOffset.Pitch != AdditionalOffsets.HandRRotOffset.Pitch) {
    				val = &AdditionalOffsets.HandRRotOffset.Pitch;
    			}
    			if (LastAddOffsets.HandRPosOffset.X != AdditionalOffsets.HandRPosOffset.X) {
    				val = &AdditionalOffsets.HandRPosOffset.X;
    			}
    			if (LastAddOffsets.HandRPosOffset.Y != AdditionalOffsets.HandRPosOffset.Y) {
    				val = &AdditionalOffsets.HandRPosOffset.Y;
    			}
    			if (LastAddOffsets.HandRPosOffset.Z != AdditionalOffsets.HandRPosOffset.Z) {
    				val = &AdditionalOffsets.HandRPosOffset.Z;
    			}
    
    			if (LastAddOffsets.LowerLegRRotOffset.Roll != AdditionalOffsets.LowerLegRRotOffset.Roll) {
    				val = &AdditionalOffsets.LowerLegRRotOffset.Roll;
    			}
    			if (LastAddOffsets.LowerLegRRotOffset.Yaw != AdditionalOffsets.LowerLegRRotOffset.Yaw) {
    				val = &AdditionalOffsets.LowerLegRRotOffset.Yaw;
    			}
    			if (LastAddOffsets.LowerLegRRotOffset.Pitch != AdditionalOffsets.LowerLegRRotOffset.Pitch) {
    				val = &AdditionalOffsets.LowerLegRRotOffset.Pitch;
    			}
    			if (LastAddOffsets.LowerLegRPosOffset.X != AdditionalOffsets.LowerLegRPosOffset.X) {
    				val = &AdditionalOffsets.LowerLegRPosOffset.X;
    			}
    			if (LastAddOffsets.LowerLegRPosOffset.Y != AdditionalOffsets.LowerLegRPosOffset.Y) {
    				val = &AdditionalOffsets.LowerLegRPosOffset.Y;
    			}
    			if (LastAddOffsets.LowerLegRPosOffset.Z != AdditionalOffsets.LowerLegRPosOffset.Z) {
    				val = &AdditionalOffsets.LowerLegRPosOffset.Z;
    			}
    
    			if (LastAddOffsets.LowerLegLRotOffset.Roll != AdditionalOffsets.LowerLegLRotOffset.Roll) {
    				val = &AdditionalOffsets.LowerLegLRotOffset.Roll;
    			}
    			if (LastAddOffsets.LowerLegLRotOffset.Yaw != AdditionalOffsets.LowerLegLRotOffset.Yaw) {
    				val = &AdditionalOffsets.LowerLegLRotOffset.Yaw;
    			}
    			if (LastAddOffsets.LowerLegLRotOffset.Pitch != AdditionalOffsets.LowerLegLRotOffset.Pitch) {
    				val = &AdditionalOffsets.LowerLegLRotOffset.Pitch;
    			}
    			if (LastAddOffsets.LowerLegLPosOffset.X != AdditionalOffsets.LowerLegLPosOffset.X) {
    				val = &AdditionalOffsets.LowerLegLPosOffset.X;
    			}
    			if (LastAddOffsets.LowerLegLPosOffset.Y != AdditionalOffsets.LowerLegLPosOffset.Y) {
    				val = &AdditionalOffsets.LowerLegLPosOffset.Y;
    			}
    			if (LastAddOffsets.LowerLegLPosOffset.Z != AdditionalOffsets.LowerLegLPosOffset.Z) {
    				val = &AdditionalOffsets.LowerLegLPosOffset.Z;
    			}
    
    			if (LastAddOffsets.LowerArmLRotOffset.Roll != AdditionalOffsets.LowerArmLRotOffset.Roll) {
    				val = &AdditionalOffsets.LowerArmLRotOffset.Roll;
    			}
    			if (LastAddOffsets.LowerArmLRotOffset.Yaw != AdditionalOffsets.LowerArmLRotOffset.Yaw) {
    				val = &AdditionalOffsets.LowerArmLRotOffset.Yaw;
    			}
    			if (LastAddOffsets.LowerArmLRotOffset.Pitch != AdditionalOffsets.LowerArmLRotOffset.Pitch) {
    				val = &AdditionalOffsets.LowerArmLRotOffset.Pitch;
    			}
    			if (LastAddOffsets.LowerArmLPosOffset.X != AdditionalOffsets.LowerArmLPosOffset.X) {
    				val = &AdditionalOffsets.LowerArmLPosOffset.X;
    			}
    			if (LastAddOffsets.LowerArmLPosOffset.Y != AdditionalOffsets.LowerArmLPosOffset.Y) {
    				val = &AdditionalOffsets.LowerArmLPosOffset.Y;
    			}
    			if (LastAddOffsets.LowerArmLPosOffset.Z != AdditionalOffsets.LowerArmLPosOffset.Z) {
    				val = &AdditionalOffsets.LowerArmLPosOffset.Z;
    			}
    
    			if (LastAddOffsets.LowerArmRRotOffset.Roll != AdditionalOffsets.LowerArmRRotOffset.Roll) {
    				val = &AdditionalOffsets.LowerArmRRotOffset.Roll;
    			}
    			if (LastAddOffsets.LowerArmRRotOffset.Yaw != AdditionalOffsets.LowerArmRRotOffset.Yaw) {
    				val = &AdditionalOffsets.LowerArmRRotOffset.Yaw;
    			}
    			if (LastAddOffsets.LowerArmRRotOffset.Pitch != AdditionalOffsets.LowerArmRRotOffset.Pitch) {
    				val = &AdditionalOffsets.LowerArmRRotOffset.Pitch;
    			}
    			if (LastAddOffsets.LowerArmRPosOffset.X != AdditionalOffsets.LowerArmRPosOffset.X) {
    				val = &AdditionalOffsets.LowerArmRPosOffset.X;
    			}
    			if (LastAddOffsets.LowerArmRPosOffset.Y != AdditionalOffsets.LowerArmRPosOffset.Y) {
    				val = &AdditionalOffsets.LowerArmRPosOffset.Y;
    			}
    			if (LastAddOffsets.LowerArmRPosOffset.Z != AdditionalOffsets.LowerArmRPosOffset.Z) {
    				val = &AdditionalOffsets.LowerArmRPosOffset.Z;
    			}
    
    			if (LastAddOffsets.LowerBodyRotOffset.Roll != AdditionalOffsets.LowerBodyRotOffset.Roll) {
    				val = &AdditionalOffsets.LowerBodyRotOffset.Roll;
    			}
    			if (LastAddOffsets.LowerBodyRotOffset.Yaw != AdditionalOffsets.LowerBodyRotOffset.Yaw) {
    				val = &AdditionalOffsets.LowerBodyRotOffset.Yaw;
    			}
    			if (LastAddOffsets.LowerBodyRotOffset.Pitch != AdditionalOffsets.LowerBodyRotOffset.Pitch) {
    				val = &AdditionalOffsets.LowerBodyRotOffset.Pitch;
    			}
    			if (LastAddOffsets.LowerBodyPosOffset.X != AdditionalOffsets.LowerBodyPosOffset.X) {
    				val = &AdditionalOffsets.LowerBodyPosOffset.X;
    			}
    			if (LastAddOffsets.LowerBodyPosOffset.Y != AdditionalOffsets.LowerBodyPosOffset.Y) {
    				val = &AdditionalOffsets.LowerBodyPosOffset.Y;
    			}
    			if (LastAddOffsets.LowerBodyPosOffset.Z != AdditionalOffsets.LowerBodyPosOffset.Z) {
    				val = &AdditionalOffsets.LowerBodyPosOffset.Z;
    			}
    
    			if (LastAddOffsets.UpperBodyRotOffset.Roll != AdditionalOffsets.UpperBodyRotOffset.Roll) {
    				val = &AdditionalOffsets.UpperBodyRotOffset.Roll;
    			}
    			if (LastAddOffsets.UpperBodyRotOffset.Yaw != AdditionalOffsets.UpperBodyRotOffset.Yaw) {
    				val = &AdditionalOffsets.UpperBodyRotOffset.Yaw;
    			}
    			if (LastAddOffsets.UpperBodyRotOffset.Pitch != AdditionalOffsets.UpperBodyRotOffset.Pitch) {
    				val = &AdditionalOffsets.UpperBodyRotOffset.Pitch;
    			}
    			if (LastAddOffsets.UpperBodyPosOffset.X != AdditionalOffsets.UpperBodyPosOffset.X) {
    				val = &AdditionalOffsets.UpperBodyPosOffset.X;
    			}
    			if (LastAddOffsets.UpperBodyPosOffset.Y != AdditionalOffsets.UpperBodyPosOffset.Y) {
    				val = &AdditionalOffsets.UpperBodyPosOffset.Y;
    			}
    			if (LastAddOffsets.UpperBodyPosOffset.Z != AdditionalOffsets.UpperBodyPosOffset.Z) {
    				val = &AdditionalOffsets.UpperBodyPosOffset.Z;
    			}
    		}
    
    		if (val != nullptr) {
    			UObject* DefaultObject = GetClass()->GetDefaultObject();
    			//fprop->SetPropertyValue_InContainer(DefaultObject, *val);
    			LastAddOffsets = AdditionalOffsets;
    		}
    	}
    
    
    }
    #endif
    
    void AMCController::ToggleRecording() {
    	if (IsSavingToAnim) {
    		return;
    	}
    
    	if (!IsRecording) {
    		LogHandler.NewLog("DataLog" + FString::FromInt(CurRecordingInSession));
    		LogHandler.StartRecording();
    		Pawn->CalcSensorOffsets(LogHandler, UseLastOffsets, DebugMode);
    		if (DebugMode || KeepPawnVisible) {
    			Pawn->SetMeshVisibility(true);
    		}
    		else {
    			Pawn->SetMeshVisibility(false);
    		}
    
    		AnimSaveState.CurrentMarker = 0;
    
    		if (InstructionWidget) {
    			InstructionWidget->WidgetSwitcher->SetActiveWidgetIndex(InstructionWidget->RecordIndex);
    		}
    		if (FeedbackWidget) {
    			FeedbackWidget->RecordingStateText->SetText(FText::FromString("Press 'SetMarker' to begin!"));
    			FeedbackWidget->RecordingStateText->SetColorAndOpacity(FSlateColor(FLinearColor::White));
    			FeedbackWidget->SensorBox->SetVisibility(ESlateVisibility::Visible);
    			FeedbackWidget->AnimNameText->SetText(FText::FromString(NameOfRecording));
    			FeedbackWidget->ProgBar->SetVisibility(ESlateVisibility::Hidden);
    		}
    	}
    	else {
    		if (!KeepPawnInvisible) {
    			Pawn->SetMeshVisibility(true);
    		}
    		LogHandler.StopRecording();
    		LogHandler.WriteToLogFile();
    		FString OldName = NameOfRecording;
    		LogHandler.CopyLogToRecordings(NameOfRecording);
    
    		if (OldName != NameOfRecording) {
    			if (InstructionWidget) {
    				InstructionWidget->FeedbackText->SetText(FText::FromString("Recording Done!\nNameOfRecording changed to " + NameOfRecording));
    			}
    			if (OutputMsgOnScreen) {
    				GEngine->AddOnScreenDebugMessage(-1, 30.0f, FColor::Yellow, FString::Printf(TEXT("NameOfRecording changed to %s. If you want to use this recording in future play sessions, please update the property on MCController."), *NameOfRecording));
    			}
    		}
    
    		if (InstructionWidget) {
    			InstructionWidget->FeedbackText->SetText(FText::FromString("Recording Done!"));
    			InstructionWidget->WidgetSwitcher->SetActiveWidgetIndex(InstructionWidget->StartIndex);
    		}
    		if (FeedbackWidget) {
    			FeedbackWidget->RecordingStateText->SetText(FText::FromString(""));
    			FeedbackWidget->SensorBox->SetVisibility(ESlateVisibility::Hidden);
    			FeedbackWidget->LeftFingerText->SetText(FText::FromString(""));
    			FeedbackWidget->RightFingerText->SetText(FText::FromString(""));
    			FeedbackWidget->AnimNameText->SetText(FText::FromString(""));
    		}
    
    		CurRecordingInSession++;
    	}
    
    	if (DebugMode) {
    		Pawn->GetAnimInstance()->DoRig = true;
    	}
    	else {
    		Pawn->GetAnimInstance()->DoRig = false;
    	}
    
    	IsRecording = !IsRecording;
    }
    
    void AMCController::SetMarker() {
    	if (IsRecording) {
    		AnimSaveState.CurrentMarker++;
    		LogHandler.SetMarker();
    		if (FeedbackWidget) {
    			FeedbackWidget->RecordingStateText->SetText(FText::FromString("Recording Anim " + FString::FromInt(AnimSaveState.CurrentMarker)));
    			FeedbackWidget->RecordingStateText->SetColorAndOpacity(FSlateColor(FLinearColor::Yellow));
    		}
    	}
    }
    
    void AMCController::SaveAnimationEditor() {
    	SaveAnimation({}, false);
    }
    
    void AMCController::SaveAnimation(const TArray<float>& HaltingPoints, bool skipTranslation) {
    
    	if (IsSavingToAnim) {
    		return;
    	}
    
    	if (IsRecording) {
    		ToggleRecording();
    	}
    
    	AnimSaveState = FAnimSaveState();
    
    	FString PathSaved = FPaths::ProjectSavedDir() + "Recordings/" + NameOfRecording;
    	if (!FPaths::DirectoryExists(PathSaved)) {
    		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("Recording Path does not exist!")));
    		return;
    	}
    
    	if (!FPaths::FileExists(PathSaved + "/DataLog.log")) {
    
    		if (!FPaths::FileExists(PathSaved + "/LogSourcePath.txt")) {
    			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("Recording Path Folder is not complete!")));
    			return;
    		}
    
    		FString LogSourcePath;
    		FFileHelper::LoadFileToString(LogSourcePath, *(PathSaved + "/LogSourcePath.txt"));
    		LogSourcePath = FPaths::ProjectSavedDir() + "OwnLogs/" + LogSourcePath;
    
    		if (!FPaths::FileExists(LogSourcePath)) {
    			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, FString::Printf(TEXT("The corresponding Log to this recording name in OwnLogs is not existent!")));
    			return;
    		}
    
    		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
    		PlatformFile.CopyFile(*(PathSaved + "/DataLog.log"), *LogSourcePath);
    		PathSaved = LogSourcePath;
    	}
    	else {
    		PathSaved = PathSaved + "/DataLog.log";
    	}
    
    	if (InstructionWidget) {
    		InstructionWidget->WidgetSwitcher->SetActiveWidgetIndex(InstructionWidget->SavingIndex);
    	}
    	if (GEngine) {
    		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, FString::Printf(TEXT("Saving Animation...")));
    	}
    
    	if (FeedbackWidget) {
    		FeedbackWidget->ProgBar->SetVisibility(ESlateVisibility::Visible);
    		FeedbackWidget->ProgBar->SetPercent(0);
    		FeedbackWidget->AnimNameText->SetText(FText::FromString(NameOfRecording));
    	}
    
    	FFileHelper::LoadFileToStringArray(AnimSaveState.StringData, *PathSaved);
    
    	AnimSaveState.CurrentEntryIndex = 0;
    	AnimSaveState.CurrentMarker = 0;
    
    	AnimSaveState.Pawn = Pawn;
    	AnimSaveState.Pawn->GetAnimInstance()->DoFingers = bFingerTrackingEnabled;
    	AnimSaveState.Pawn->GetAnimInstance()->LockFeet = LockFeet;
    	AnimSaveState.Pawn->GetAnimInstance()->UseHandPos = UseHandPosition;
    	AnimSaveState.Pawn->GetAnimInstance()->LimitHandRot = LimitHandRotation;
    	AnimSaveState.Pawn->GetAnimInstance()->SnapshotAnimations.Empty();
    
    	AnimSaveState.FPS = FramesPerSecond;
    	AnimSaveState.SPF = 1.f / ((float)AnimSaveState.FPS);
    
    	IsSavingToAnim = !skipTranslation;
    	if (skipTranslation) {
    		return;
    	}
    	if (!EditOffsetMode) {
    		AnimSaveState.WaitForAnimInstance = true;
    	}
    
    	AnimSaveState.Pawn->GetAnimInstance()->AdditionalOffsets = AdditionalOffsets;
    
    	bool isValid = PreprocessRecording(HaltingPoints);
    	if (!isValid) {
    		IsSavingToAnim = false;
    		return;
    	}
    
    	AnimSaveState.Pawn->GetAnimInstance()->DoRig = true;
    
    	InputNextFrame();
    	
    }