From ea4bab70362d2d41977b8ea96ebc8b3f8229d53f Mon Sep 17 00:00:00 2001
From: pnossol <patrick.nossol@gmail.com>
Date: Tue, 31 May 2022 09:27:49 +0200
Subject: [PATCH] Implemented preprocessing of data and other tweaks

---
 Content/MoCapMap.umap                       |   4 +-
 Content/SaveSequenceRig.uasset              |   4 +-
 Source/MoCapPlugin/Private/MCController.cpp | 541 ++++++++++++++++++--
 Source/MoCapPlugin/Private/MCDefines.cpp    |  38 ++
 Source/MoCapPlugin/Private/MCPawn.cpp       |  54 +-
 Source/MoCapPlugin/Private/MCRigUnits.cpp   |  67 ++-
 Source/MoCapPlugin/Public/MCController.h    |  18 +-
 Source/MoCapPlugin/Public/MCDefines.h       |   4 +
 Source/MoCapPlugin/Public/MCPawn.h          |   4 +
 Source/MoCapPlugin/Public/MCRigUnits.h      |  27 +-
 10 files changed, 706 insertions(+), 55 deletions(-)
 create mode 100644 Source/MoCapPlugin/Private/MCDefines.cpp

diff --git a/Content/MoCapMap.umap b/Content/MoCapMap.umap
index 4fd773a..6d1167a 100644
--- a/Content/MoCapMap.umap
+++ b/Content/MoCapMap.umap
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:247c65dcafd56213adf01bf90c1e7fa6afd34c321deec48c8f618e658f5d0237
-size 39920
+oid sha256:92a5fa84a7731e65598ce589a146da254ef412238115d8187d6ec7ab5c455170
+size 39985
diff --git a/Content/SaveSequenceRig.uasset b/Content/SaveSequenceRig.uasset
index 6bc7ca4..dc8dfa1 100644
--- a/Content/SaveSequenceRig.uasset
+++ b/Content/SaveSequenceRig.uasset
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:29a5de877c6fd233d0416e428dd532fed364376333c5fd7d47eba29a07779288
-size 4080636
+oid sha256:62d3c42edf0f9d2db2d91756dcc93681e4c7f7d801973e757ae9840ae0efbc4a
+size 4560157
diff --git a/Source/MoCapPlugin/Private/MCController.cpp b/Source/MoCapPlugin/Private/MCController.cpp
index 25314bd..d2a6025 100644
--- a/Source/MoCapPlugin/Private/MCController.cpp
+++ b/Source/MoCapPlugin/Private/MCController.cpp
@@ -136,7 +136,7 @@ void AMCController::SaveToAnimMode() {
 	}
 
 	if (!AnimSaveState.WaitForAnimInstance) {
-		if (AnimSaveState.CurrentEntryIndex < AnimSaveState.StringData.Num()) {
+		if (AnimSaveState.CurrentEntryIndex < AnimSaveState.AnimData.Num()) {
 			InputNextFrame();
 		}
 		else {
@@ -155,83 +155,552 @@ void AMCController::SaveToAnimMode() {
 
 }
 
-void AMCController::InputNextFrame() {
+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;
 
-	bool stop = false;
-	UMCAnimInstance* AI = AnimSaveState.Pawn->GetAnimInstance();
+	TArray<FProcessedAnimData> OldAnimData;
+	for (int i = 0; i < AnimSaveState.AnimData.Num(); i++) {
+		OldAnimData.Push(AnimSaveState.AnimData[i]);
+	}
 
-	for (; !stop && AnimSaveState.CurrentEntryIndex < AnimSaveState.StringData.Num();) {
+	for (int i = end + 1; i < AnimSaveState.AnimData.Num(); i++) {
+		AnimSaveState.AnimData[i].Timestamp += FTimespan::FromSeconds(DeltaTime);
+	}
 
-		auto Entry = AnimSaveState.StringData[AnimSaveState.CurrentEntryIndex];
+	AnimSaveState.AnimData.RemoveAt(start, OldNrFrames, true);
 
-		FString TimeString, JsonString;
-		Entry.Split(" ", &TimeString, &JsonString);
-		FTimespan Timestamp = MCUtils::StringToTimespan(TimeString);
+	int CurOldIndex = start;
 
-		// Check if Timestemp went over the 1 hour mark
-		if (Timestamp < AnimSaveState.LastMarker) {
-			Timestamp = Timestamp + FTimespan::FromHours(1);
+	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), 4);
+		//SmoothStart
+		float TransformedI = FMath::Pow(((float)i / (float)NewNrFrames), 4);
+		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++;
 		}
 
-		// Check if next frame should be taken
-		if (AnimSaveState.CurrentMarker > 0 && Timestamp > AnimSaveState.NextFrame) {
+		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();
+		}
 
-			AnimSaveState.Pawn->AICalcFrame();
 
-			AnimSaveState.NextFrame = AnimSaveState.NextFrame + FTimespan::FromSeconds(AnimSaveState.SPF);
-			stop = true;
-			AnimSaveState.WaitForAnimInstance = true;
+		//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;
+
+			}
 
-			FeedbackWidget->ProgBar->SetPercent((float)AnimSaveState.CurrentEntryIndex / (float)AnimSaveState.StringData.Num());
+		}
+
+		AnimSaveState.AnimData.Insert(Data, start + i);
+
+	}
+
+}
 
+void AMCController::PreprocessRecording() {
+
+	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")) {
-			AnimSaveState.CurrentEntryIndex++;
 			continue;
 		}
 
 		FString Type = TmpJson->GetStringField("Type");
 
 		if (Type == "Start") {
-			AnimSaveState.LastMarker = Timestamp;
-			AnimSaveState.NextFrame = Timestamp;
+			LastStamp = Timestamp;
+			NextFrame = Timestamp;
 		}
 		else if (Type == "Marker" || Type == "End") {
 
-			AnimSaveState.LastMarker = Timestamp;
-			AnimSaveState.NextFrame = Timestamp;
+			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);
+		}
+
+	}
+
+	//----pull down hip amount----
+
+	float PullDownHip = 0;
+	{
+		FVector OffsetPos = AnimSaveState.AnimData[0].SensorData.LowerBody.Pos;
+		float diff = OffsetPos.Z;
+		FQuat OffsetRot = AnimSaveState.AnimData[0].SensorData.LowerBody.Rot;
+		OffsetRot = OffsetRot * FQuat(FVector(0, 0, 1), FMath::DegreesToRadians(180));
+		AnimSaveState.Pawn->OffsetSensorData(EBodyPart::LowerBody, OffsetPos, OffsetRot);
+		diff = OffsetPos.Z - diff;
+		PullDownHip = diff;
+	}
+
+	//----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;
+
+				}
 
+				
+
+			}
+
+		}
+
+	}
+
+	//---other processing---
+	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);
+
+			//pull down hip
+			if (Type == EBodyPart::LowerBody) {
+				FQuat Swing, Twist;
+				Entry->Rot.ToSwingTwist(FVector(0, 0, 1), Swing, Twist);
+				float angle = FMath::RadiansToDegrees(Swing.GetAngle());
+				if (angle > 180) {
+					angle -= 360;
+				}
+				//Entry.Pos.Z += PullDownHip * FMath::Abs(angle / 180.f);
+			}
+
+			//analyse z pos of feet
+			if (Type == EBodyPart::LowerLegL) {
+
+				int window = 5;
+				bool isMin = true;
+				for (int w = 1; w <= window; w++) {
+					if (i + w < AnimSaveState.AnimData.Num()) {
+						FVector Pos = AnimSaveState.AnimData[i + w].SensorData.LowerLegL.Pos;
+						if (Entry->Pos.Z <= Pos.Z) {
+							isMin = false;
+							break;
+						}
+					}
+					if (i - w >= 0) {
+						FVector Pos = AnimSaveState.AnimData[i - w].SensorData.LowerLegL.Pos;
+						if (Entry->Pos.Z <= Pos.Z) {
+							isMin = false;
+							break;
+						}
+					}
+				}
+
+				if (isMin) {
+					AnimSaveState.LowestPoints.Push(Entry->Pos.Z);
+				}
+
+				//offset
+
+				FVector OffsetPos = Entry->Pos;
+				FQuat OffsetRot = Entry->Rot;
+				AnimSaveState.Pawn->OffsetSensorData(Type, OffsetPos, OffsetRot);
+
+				isMin = true;
+				for (int w = 1; w <= window; w++) {
+					if (i + w < AnimSaveState.AnimData.Num()) {
+						FVector Pos = AnimSaveState.AnimData[i + w].SensorData.LowerLegL.Pos;
+						if (OffsetPos.Z <= Pos.Z) {
+							isMin = false;
+							break;
+						}
+					}
+					if (i - w >= 0) {
+						FVector Pos = AnimSaveState.AnimData[i - w].SensorData.LowerLegL.Pos;
+						if (OffsetPos.Z <= Pos.Z) {
+							isMin = false;
+							break;
+						}
+					}
+				}
+
+				if (isMin) {
+					AnimSaveState.LowestPointsOffset.Push(OffsetPos.Z);
+				}
+
+			}
+
+		}
+
+		//process finger data
+		if (bFingerTrackingEnabled) {
+
+		}
+
+	}
+
+	//---find last slow part of the gesture and scale it up---
+	
+	TArray<float> GestureSpeed;
+	
+	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);
+
+	}
+	
+	AnimSaveState.NextFrame = FTimespan();
+	
+	
+	//debugging things
+	
+	std::string speed = "";
+	std::string lp = "";
+	std::string lpo = "";
+
+	for (int i = 0; i < GestureSpeed.Num(); i++) {
+		speed += "\n" + std::to_string(GestureSpeed[i]);
+	}
+
+	for (int i = 0; i < AnimSaveState.LowestPoints.Num(); i++) {
+		lp += "\n" + std::to_string(AnimSaveState.LowestPoints[i]);
+	}
+
+	for (int i = 0; i < AnimSaveState.LowestPointsOffset.Num(); i++) {
+		lpo += "\n" + std::to_string(AnimSaveState.LowestPointsOffset[i]);
+	}
+
+}
+
+
+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 = Timestamp;
+				AI->SnapshotAnimations.Last().EndTime = Entry.Timestamp;
 			}
 
-			if (Type == "Marker") {
+			if (Entry.IsMarker) {
 				AI->SnapshotAnimations.Add(FSnapshotAnimations());
-				AI->SnapshotAnimations.Last().StartTime = Timestamp;
+				AI->SnapshotAnimations.Last().StartTime = Entry.Timestamp;
 				AnimSaveState.CurrentMarker++;
 
 				FeedbackWidget->RecordingStateText->SetText(FText::FromString("Converting Anim " + FString::FromInt(AnimSaveState.CurrentMarker) + "..."));
 			}
 			else { // if (Type == "End")
+				AnimSaveState.CurrentEntryIndex++;
 				stop = true;
 			}
 		}
-		else if (Type == "ViveData") {
-			AnimSaveState.Pawn->InputViveDataToAnimInstance(TmpJson);
+
+		else {
+
+			AI->SensorData = Entry.SensorData;
 			if (bFingerTrackingEnabled) {
-				AnimSaveState.Pawn->InputFingerDataToAnimInstance(TmpJson);
+				AI->FingerData = Entry.FingerData;
 			}
-		}
-		else if (Type == "Offsets") {
-			AnimSaveState.Pawn->InputViveOffsetsToAnimInstance(TmpJson);
-		}
 
-		AnimSaveState.CurrentEntryIndex++;
+			AI->SnapshotAnimations.Last().TimeStamps.Push(Entry.Timestamp);
+
+			AnimSaveState.Pawn->AICalcFrame();
+
+			stop = true;
+			AnimSaveState.WaitForAnimInstance = true;
+
+			FeedbackWidget->ProgBar->SetPercent((float)AnimSaveState.CurrentEntryIndex / (float)AnimSaveState.AnimData.Num());
+
+		}
+		
 
 	}
 
@@ -503,6 +972,8 @@ void AMCController::SaveAnimation() {
 	IsSavingToAnim = true;
 	AnimSaveState.WaitForAnimInstance = true;
 
+	PreprocessRecording();
+
 	InputNextFrame();
 	
 }
diff --git a/Source/MoCapPlugin/Private/MCDefines.cpp b/Source/MoCapPlugin/Private/MCDefines.cpp
new file mode 100644
index 0000000..df56a0e
--- /dev/null
+++ b/Source/MoCapPlugin/Private/MCDefines.cpp
@@ -0,0 +1,38 @@
+#include "MCDefines.h"
+
+FSensorDataEntry* FSensorData::GetEntry(EBodyPart BodyPart) {
+	switch (BodyPart) {
+	case EBodyPart::Head:
+		return &Head;
+	case EBodyPart::HandL:
+		return &HandL;
+	case EBodyPart::HandR:
+		return &HandR;
+	case EBodyPart::FootL:
+		return &FootL;
+	case EBodyPart::FootR:
+		return &FootR;
+	case EBodyPart::UpperArmL:
+		return &UpperArmL;
+	case EBodyPart::UpperArmR:
+		return &UpperArmR;
+	case EBodyPart::LowerArmL:
+		return &LowerArmL;
+	case EBodyPart::LowerArmR:
+		return &LowerArmR;
+	case EBodyPart::UpperLegL:
+		return &UpperLegL;
+	case EBodyPart::UpperLegR:
+		return &UpperLegR;
+	case EBodyPart::LowerLegL:
+		return &LowerLegL;
+	case EBodyPart::LowerLegR:
+		return &LowerLegR;
+	case EBodyPart::UpperBody:
+		return &UpperBody;
+	case EBodyPart::LowerBody:
+		return &LowerBody;
+	default:
+		return nullptr;
+	}
+}
diff --git a/Source/MoCapPlugin/Private/MCPawn.cpp b/Source/MoCapPlugin/Private/MCPawn.cpp
index 99f019f..b8a6454 100644
--- a/Source/MoCapPlugin/Private/MCPawn.cpp
+++ b/Source/MoCapPlugin/Private/MCPawn.cpp
@@ -27,8 +27,9 @@ AMCPawn::AMCPawn(const FObjectInitializer& ObjectInitializer)
 	SkeletalMesh->SetVisibility(true, true);
 	SkeletalMesh->SetupAttachment(RootComponent);
 
-	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerLegL, "Tracker_Foot_Left", BoneNames.calf_l));
-	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerLegR, "Tracker_Foot_Right", BoneNames.calf_r));
+	
+	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerLegL, "Tracker_Foot_Left", BoneNames.foot_l));
+	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerLegR, "Tracker_Foot_Right", BoneNames.foot_r));
 	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerArmL, "Tracker_Elbow_Left", BoneNames.lowerarm_l));
 	SensorSetup.Trackers.Add(FSensor(EBodyPart::LowerArmR, "Tracker_Elbow_Right", BoneNames.lowerarm_r));
 	SensorSetup.Trackers.Add(FSensor(EBodyPart::UpperBody, "Tracker_Chest", BoneNames.spine03));
@@ -413,13 +414,14 @@ void AMCPawn::InputViveDataToAnimInstance(TSharedPtr<FJsonObject> Data) {
 			const float Roll = FCString::Atof(*JsonObj->GetStringField("Roll"));
 			const float Pitch = FCString::Atof(*JsonObj->GetStringField("Pitch"));
 			const float Yaw = FCString::Atof(*JsonObj->GetStringField("Yaw"));
+			FRotator Rot(Pitch, Yaw, Roll);
 
 			const float PosX = FCString::Atof(*JsonObj->GetStringField("PosX"));
 			const float PosY = FCString::Atof(*JsonObj->GetStringField("PosY"));
 			const float PosZ = FCString::Atof(*JsonObj->GetStringField("PosZ"));
+			FVector Pos(PosX, PosY, PosZ);
 
-			AI->SetSensorData((EBodyPart)i, FVector(PosX, PosY, PosZ), FRotator(Pitch, Yaw, Roll).Quaternion());
-
+			AI->SetSensorData((EBodyPart)i, Pos, Rot.Quaternion());
 		}
 	}
 
@@ -485,6 +487,50 @@ void AMCPawn::InputFingerDataToAnimInstance(TSharedPtr<FJsonObject> Data) {
 
 }
 
+void AMCPawn::OffsetSensorData(EBodyPart part, FVector & Pos, FQuat & Rot) {
+
+	FSensorOffset* Offset = nullptr;
+	UMCAnimInstance* AI = GetAnimInstance();
+	
+	switch (part) {
+	case EBodyPart::Head:
+		Offset = &AI->SensorOffsets.Head;
+		break;
+	case EBodyPart::HandL:
+		Offset = &AI->SensorOffsets.HandL;
+		break;
+	case EBodyPart::HandR:
+		Offset = &AI->SensorOffsets.HandR;
+		break;
+	case EBodyPart::LowerLegL:
+	case EBodyPart::FootL:
+		Offset = &AI->SensorOffsets.LowerLegL;
+		break;
+	case EBodyPart::LowerLegR:
+	case EBodyPart::FootR:
+		Offset = &AI->SensorOffsets.LowerLegR;
+		break;
+	case EBodyPart::LowerArmL:
+		Offset = &AI->SensorOffsets.LowerArmL;
+		break;
+	case EBodyPart::LowerArmR:
+		Offset = &AI->SensorOffsets.LowerArmR;
+		break;
+	case EBodyPart::LowerBody:
+		Offset = &AI->SensorOffsets.LowerBody;
+		break;
+	case EBodyPart::UpperBody:
+		Offset = &AI->SensorOffsets.UpperBody;
+		break;
+	}
+
+	FQuat RotTmp = Rot;
+
+	Rot = Rot * Offset->Rot;
+	Pos = Pos - Offset->Distance * (Offset->AxisRotDiff * RotTmp).GetAxisX();
+
+}
+
 void AMCPawn::AddSensorDataToJson(TSharedPtr<FJsonObject> JsonObjectFull, const FSensor& Sensor) {
 	TSharedPtr<FJsonObject> JsonObject = MAKE_JSON;
 
diff --git a/Source/MoCapPlugin/Private/MCRigUnits.cpp b/Source/MoCapPlugin/Private/MCRigUnits.cpp
index 72cfa98..950e55b 100644
--- a/Source/MoCapPlugin/Private/MCRigUnits.cpp
+++ b/Source/MoCapPlugin/Private/MCRigUnits.cpp
@@ -154,21 +154,74 @@ FRigUnit_ApplyFingerData_Execute() {
 	}
 }
 
-FRigUnit_CheckLengths::FRigUnit_CheckLengths() {
+FRigUnit_FootLocking::FRigUnit_FootLocking() {
+
+	FloorOffset = 0.f;
+	ReleaseHeight = 0.2f;
+	ReleaseDistHorizontal = 0.3f;
+
+	LockedLeft = false;
+	LockedRight = false;
 
 }
 
-FRigUnit_CheckLengths_Execute() {
+FRigUnit_FootLocking_Execute() {
 	DECLARE_SCOPE_HIERARCHICAL_COUNTER_RIGUNIT()
 
-	FRigBoneHierarchy* Hierarchy = ExecuteContext.GetBones();
+	FRigBoneHierarchy* Bones = ExecuteContext.GetBones();
+	FRigControlHierarchy* Controls = ExecuteContext.GetControls();
 
-	if (Hierarchy) {
+	if (Bones && Controls) {
+
+		FName FootBoneName;
+		bool* Locked = nullptr;
+		FTransform* LockedTrans = nullptr;
+
+		for (int i = 0; i < 2; i++) {
+
+			if (i == 0) {
+				FootBoneName = "CalfL";
+				Locked = &LockedLeft;
+				LockedTrans = &LockedTransLeft;
+			}
+			else {
+				FootBoneName = "CalfR";
+				Locked = &LockedRight;
+				LockedTrans = &LockedTransRight;
+			}
+
+			FTransform Root = Bones->GetGlobalTransform("root");
+			FTransform Foot = Controls->GetGlobalTransform(FootBoneName);
 
-		FTransform Trans1 = Hierarchy->GetGlobalTransform("pelvis");
-		FTransform Trans2 = Hierarchy->GetGlobalTransform("neck_01");
+			FVector RootTrans2D = Root.GetLocation();
+			float RootHeight = RootTrans2D.Z;
+			RootTrans2D.Z = 0.f;
+			FVector FootTrans2D = Root.GetLocation();
+			float FootHeight = FootTrans2D.Z;
+			FootTrans2D.Z = 0.f;
 
-		float Dist = FVector::Dist(Trans1.GetLocation(), Trans2.GetLocation());
+			if (*Locked) {
+
+				float Dist2D = FVector::Dist(RootTrans2D, FootTrans2D);
+
+				if (FootHeight > RootHeight + FloorOffset + ReleaseHeight || Dist2D > ReleaseDistHorizontal) {
+					*Locked = false;
+				}
+				else {
+					Controls->SetGlobalTransform(FootBoneName, *LockedTrans);
+				}
+
+			}
+			else {
+
+				if (FootHeight < RootHeight + FloorOffset) {
+					*Locked = true;
+					*LockedTrans = Foot;
+				}
+
+			}
+
+		}
 
 	}
 }
\ No newline at end of file
diff --git a/Source/MoCapPlugin/Public/MCController.h b/Source/MoCapPlugin/Public/MCController.h
index 63692fc..0f4dab3 100644
--- a/Source/MoCapPlugin/Public/MCController.h
+++ b/Source/MoCapPlugin/Public/MCController.h
@@ -16,14 +16,23 @@
 
 #include "MCController.generated.h"
 
-class USerial;
-class UPoseAsset;
+struct FProcessedAnimData {
+	bool IsMarker = false;
+	bool IsEnd = false;
+	FTimespan Timestamp;
+	FFingerData FingerData;
+	FSensorData SensorData;
+};
 
 USTRUCT()
 struct FAnimSaveState {
 	GENERATED_BODY()
 
+	TArray<float> LowestPoints;
+	TArray<float> LowestPointsOffset;
+
 	TArray<FString> StringData;
+	TArray<FProcessedAnimData> AnimData;
 	int CurrentEntryIndex;
 	int CurrentMarker;
 	FTimespan LastMarker;
@@ -79,6 +88,8 @@ protected:
 	void RecordMode();
 	void SaveToAnimMode();
 
+	void ScaleAnimDataInterval(int start, int end, float DeltaTime);
+	void PreprocessRecording();
 	void InputNextFrame();
 
 	void SaveAnimSnapshots();
@@ -128,4 +139,7 @@ public:
 	UPROPERTY(EditAnywhere, meta = (DisplayName = "Spectator Cam", Category = "MotionCapture"))
 	ASceneCapture2D* SpectatorCam;
 
+	UPROPERTY(EditAnywhere, meta = (DisplayName = "Gesture Hold Scale Excess Time", Category = "MotionCapture"))
+	float GestureHoldExcessTime = 0.f;
+
 };
diff --git a/Source/MoCapPlugin/Public/MCDefines.h b/Source/MoCapPlugin/Public/MCDefines.h
index ae4ed36..335a5ab 100644
--- a/Source/MoCapPlugin/Public/MCDefines.h
+++ b/Source/MoCapPlugin/Public/MCDefines.h
@@ -96,6 +96,8 @@ struct FSnapshotAnimations {
 	UPROPERTY(BlueprintReadWrite)
 	TArray<FPoseSnapshot> Snapshots;
 
+	TArray<FTimespan> TimeStamps;
+
 	int Fps;
 	FTimespan StartTime;
 	FTimespan EndTime;
@@ -169,6 +171,8 @@ struct FSensorData {
 	UPROPERTY(BlueprintReadOnly)
 	FSensorDataEntry LowerBody;
 
+	FSensorDataEntry* GetEntry(EBodyPart BodyPart);
+
 };
 
 USTRUCT(BlueprintType)
diff --git a/Source/MoCapPlugin/Public/MCPawn.h b/Source/MoCapPlugin/Public/MCPawn.h
index ea0ae7b..47acc9b 100644
--- a/Source/MoCapPlugin/Public/MCPawn.h
+++ b/Source/MoCapPlugin/Public/MCPawn.h
@@ -26,6 +26,8 @@ class MOCAPPLUGIN_API AMCPawn : public APawn
 
 public:
 
+	bool UseKneeAsFoot;
+
 	UPROPERTY(BlueprintReadOnly)
 	FSensorSetup SensorSetup;
 
@@ -64,6 +66,8 @@ public:
 	void InputViveOffsetsToAnimInstance(TSharedPtr<FJsonObject> Data);
 	void InputFingerDataToAnimInstance(TSharedPtr<FJsonObject> Data);
 
+	void OffsetSensorData(EBodyPart part, FVector& Pos, FQuat& Rot);
+
 private:
 
 	void AddSensorDataToJson(TSharedPtr<FJsonObject> JsonObjectFull, const FSensor& Sensor);
diff --git a/Source/MoCapPlugin/Public/MCRigUnits.h b/Source/MoCapPlugin/Public/MCRigUnits.h
index e1edeac..a6668e4 100644
--- a/Source/MoCapPlugin/Public/MCRigUnits.h
+++ b/Source/MoCapPlugin/Public/MCRigUnits.h
@@ -73,14 +73,35 @@ struct FRigUnit_ApplyFingerData : public FRigUnitMutable
 
 };
 
-USTRUCT(meta = (DisplayName = "CheckLengths", PrototypeName = "CheckLengths", Category = "Hierarchy", NodeColor = "0.05 0.25 0.05"))
-struct FRigUnit_CheckLengths : public FRigUnitMutable
+USTRUCT(meta = (DisplayName = "FootLocking", PrototypeName = "FootLocking", Category = "Hierarchy", NodeColor = "0.05 0.25 0.05"))
+struct FRigUnit_FootLocking : public FRigUnitMutable
 {
 	GENERATED_BODY()
 
-	FRigUnit_CheckLengths();
+	FRigUnit_FootLocking();
 
 	RIGVM_METHOD()
 	virtual void Execute(const FRigUnitContext& Context) override;
 
+	UPROPERTY(meta = (Input))
+	float FloorOffset;
+
+	UPROPERTY(meta = (Input))
+	float ReleaseHeight;
+
+	UPROPERTY(meta = (Input))
+	float ReleaseDistHorizontal;
+
+	UPROPERTY()
+	bool LockedLeft;
+
+	UPROPERTY()
+	bool LockedRight;
+
+	UPROPERTY()
+	FTransform LockedTransLeft;
+
+	UPROPERTY()
+	FTransform LockedTransRight;
+
 };
\ No newline at end of file
-- 
GitLab