Skip to content
Snippets Groups Projects
Commit 0d98e617 authored by Andrea Bönsch's avatar Andrea Bönsch
Browse files

Merge branch 'feature/locomotion' into 'develop'

Moved IK Foot into VHMovement, fixed dirty fix in VHMovement

See merge request VR-Group/unreal-development/character-plugin!5
parents 3f8c5b8c fc8e408c
Branches
No related tags found
No related merge requests found
......@@ -20,6 +20,10 @@
#include "Engine/World.h"
#include "Helper/CharacterPluginLogging.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "VHAnimInstance.h"
// Sets default values for this component's properties
UVHMovement::UVHMovement()
......@@ -44,11 +48,23 @@ void UVHMovement::BeginPlay()
AVirtualHumanPawn = Cast<APawn>(GetOwner());
VirtualHumanAIController = Cast<AVirtualHumanAIController>(AVirtualHumanPawn->GetController());
AVirtualHumanPtr = Cast<AVirtualHuman>(GetOwner());
CurrentWaypointIterator = 0;
IntermediatePointIterator = 0;
bChildMovement = false;
bLineMovement = false;
UCapsuleComponent* capsuleComp = AVirtualHumanPtr->GetCapsuleComponent();
IKTraceDistance = capsuleComp->GetScaledCapsuleHalfHeight();
IKOffsetRigthFoot = 0.0f;
IKOffsetLeftFoot = 0.0f;
IKInterpSpeed = 10.0f;
IKMaxOffset = 55.0f;
HipDisplacementFactor = 1.5f;
RightFootSocket = "foot_rSocket";
LeftFootSocket = "foot_lSocket";
AnimationInstance = Cast<UVHAnimInstance>(AVirtualHumanPtr->GetMesh()->GetAnimInstance());
//set child Parameters
int i = 1;
......@@ -86,6 +102,119 @@ void UVHMovement::BeginPlay()
void UVHMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Calculate IKOffset for Foot alignment
FVector MeshLocation = AVirtualHumanPtr->GetMesh()->GetComponentLocation();
// RightFoot
FHitResult Hit = IKFootTrace(RightFootSocket, IKTraceDistance);
// the location we want the foot of the VA to be
FVector HitLocationRight = Hit.Location;
FVector offsetRight = HitLocationRight - MeshLocation;
float IKOffsetRight = offsetRight.Z - IKHipOffset;
// LeftFoot
Hit = IKFootTrace(LeftFootSocket, IKTraceDistance);
// the location we want the foot of the VA to be
FVector HitLocationLeft = Hit.Location;
FVector offsetLeft = HitLocationLeft - MeshLocation;
float IKOffsetLeft = offsetLeft.Z - IKHipOffset;
// adjust the height of the VA, by lowering the hip bone
float footHeightDistance = abs(HitLocationRight.Z - HitLocationLeft.Z);
float hipOffset = 0;
// lower the hip for a more natural posture, when walking on slopes or stairs
if (footHeightDistance < 100.0f)
{
hipOffset = footHeightDistance * -0.5f * HipDisplacementFactor;
}
// adjust foot rotation
FVector normal = Hit.ImpactNormal;
FVector up = MeshLocation.UpVector;
float footRotationAngleRAD = acosf(FVector::DotProduct(normal, up));
// check if VA is walking UP or DOWN and change sign of footRotationAngle accordingly
FVector velocity = AVirtualHumanPtr->GetVelocity();
float normalToVelocity = FVector::DotProduct(normal, velocity);
if (velocity.Size() > 0) // only change when walking
{
// if angle between normal and velocity is bigger than 90 degree value is negative.
// that means VA is walking UP
if (normalToVelocity < 0)
{
bWalkingUp = true;
}
else
{
bWalkingUp = false;
}
}
// the rotation introduces a height error
// the Socket is at the front of the foot
// the bone we rotate around is at the back of the foot
FVector footVector = AVirtualHumanPtr->GetMesh()->GetBoneLocation("foot_r") - AVirtualHumanPtr->GetMesh()->GetSocketLocation(RightFootSocket);
float footLength = footVector.Size(); // the length of the shoe
float rotationErrorHeightAdjustment = tanf(footRotationAngleRAD) * footLength;
rotationErrorHeightAdjustment = FMath::Clamp(rotationErrorHeightAdjustment, 0.0f, footLength); // safety clamp
// we always use FInterpTo function to get smooth animations for all variables that we need in the Animation Blueprint
if (bWalkingUp)
{
IKFootRotationAngle = FMath::FInterpTo(IKFootRotationAngle, -FMath::RadiansToDegrees(footRotationAngleRAD), DeltaTime, IKInterpSpeed);
IKOffsetRight -= rotationErrorHeightAdjustment;
IKOffsetLeft -= rotationErrorHeightAdjustment;
}
else
{
IKFootRotationAngle = FMath::FInterpTo(IKFootRotationAngle, FMath::RadiansToDegrees(footRotationAngleRAD), DeltaTime, IKInterpSpeed);
IKOffsetRight += rotationErrorHeightAdjustment;
IKOffsetLeft += rotationErrorHeightAdjustment;
}
// if one of the offsets is still negative we have to lower the hip even further to reach the floor
if (IKOffsetLeft < 0.0f && IKOffsetLeft > -(IKMaxOffset / 2.0f))
{
hipOffset += IKOffsetLeft;
}
else if (IKOffsetRight < 0.0f && IKOffsetRight > -(IKMaxOffset / 2.0f))
{
hipOffset += IKOffsetRight;
}
hipOffset = FMath::Clamp(hipOffset, -IKMaxOffset, 0.0f);
IKHipOffset = FMath::FInterpTo(IKHipOffset, hipOffset, DeltaTime, IKInterpSpeed);
IKOffsetRight = FMath::Clamp(IKOffsetRight, 0.0f, IKMaxOffset);
IKOffsetRigthFoot = FMath::FInterpTo(IKOffsetRigthFoot, IKOffsetRight, DeltaTime, IKInterpSpeed);
IKOffsetLeft = FMath::Clamp(IKOffsetLeft, 0.0f, IKMaxOffset);
IKOffsetLeftFoot = FMath::FInterpTo(IKOffsetLeftFoot, IKOffsetLeft, DeltaTime, IKInterpSpeed);
// write variables to Animation Instance
if (AnimationInstance)
{
AnimationInstance->IKOffsetRight = -IKOffsetRigthFoot;
AnimationInstance->IKOffsetLeft = IKOffsetLeftFoot;
AnimationInstance->IKHipOffset = IKHipOffset;
AnimationInstance->IKFootRotation = IKFootRotationAngle;
}
}
void UVHMovement::MoveToWaypoint()
......@@ -291,7 +420,8 @@ TArray<AWaypoint*> UVHMovement::ShuffleArray(TArray<AWaypoint*> myArray)
*/
void UVHMovement::WaypointMovement()
{
if (!bChild)
// If we have not collected and sorted the Waypoints in the scene do so
if (Waypoints.Num() < 1)
{
AActor* AVHPawn = AVirtualHumanPawn;
UWorld* World = GetWorld();
......@@ -377,7 +507,9 @@ void UVHMovement::WaypointMovement()
break; // cannot be reached
}
//set child Parameters
// if we are the parent, we set the Waypoints for our childs
if (!bChild)
{
int i = 1;
for (TPair<AVirtualHuman*, float >& child : ChildVH)
{
......@@ -386,26 +518,48 @@ void UVHMovement::WaypointMovement()
VH_ERROR("Virtual Human child is not valid\n");
return;
}
bChild = false;
UVHMovement* childMovement = child.Key->FindComponentByClass<UVHMovement>();
childMovement->Waypoints = Waypoints;
i++;
}
}
// if we are the child, we set the Waypoints for the parent and for other childs
if (bChild)
{
UVHMovement* parentMovement = parentVH->FindComponentByClass<UVHMovement>();
if (!parentVH)
{
VH_ERROR("child has no valid parent\n");
return;
}
if (Waypoints.Num() < 1) {
parentMovement->Waypoints = Waypoints;
// in case a child's WaypointMovement is called before its parent's one, there are not waypoints yet
// Thus, DIRTY HACK: ask for the waypoints ourself
StoreAllWaypoints();
int i = 1;
for (TPair<AVirtualHuman*, float >& child : parentMovement->ChildVH)
{
if (!child.Key)
{
VH_ERROR("Virtual Human child is not valid\n");
return;
}
UVHMovement* childMovement = child.Key->FindComponentByClass<UVHMovement>();
childMovement->Waypoints = Waypoints;
i++;
}
}
if (Waypoints.Num() < 1) {
}
if (Waypoints.Num() < 1)
{
// in case we are still empty, there is no a failure
VH_ERROR("No Waypoints for WaypointGroup %i\n", WaypointGroup);
return;
}
}
if(bChild)
{
......@@ -682,4 +836,26 @@ float UVHMovement::CalculateChildSpeed(AWaypoint* nextWaypoint, FVector childPoi
return childSpeed;
}
FHitResult UVHMovement::IKFootTrace(FName Socket, float TraceDistance)
{
USkeletalMeshComponent* mesh = AVirtualHumanPtr->GetMesh();
FVector SocketLocation = mesh->GetSocketLocation(Socket);
FVector ActorLocation = AVirtualHumanPtr->GetActorLocation();
// line trace start location
FVector LineTraceStart = SocketLocation;
LineTraceStart.Z = ActorLocation.Z;
// line trace end location
FVector LineTraceEnd = SocketLocation;
LineTraceEnd.Z = SocketLocation.Z - IKTraceDistance;
FHitResult Hit;
FCollisionQueryParams TraceParams;
TraceParams.bTraceComplex = true;
TraceParams.AddIgnoredActor(AVirtualHumanPtr);
bool hitFound = GetWorld()->LineTraceSingleByChannel(Hit, LineTraceStart, LineTraceEnd, ECC_Visibility, TraceParams);
// DrawDebugLine(GetWorld(), LineTraceStart, LineTraceEnd, FColor::Cyan, false, 0.1f);
return Hit;
}
......@@ -6,8 +6,6 @@
#include "VHAnimInstance.h"
#include "VirtualHumanAIController.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/Engine.h"
......@@ -20,16 +18,6 @@ AVirtualHuman::AVirtualHuman()
AIControllerClass = AVirtualHumanAIController::StaticClass();
UCapsuleComponent* capsuleComp = GetCapsuleComponent();
IKTraceDistance = capsuleComp->GetScaledCapsuleHalfHeight();
IKOffsetRigthFoot = 0.0f;
IKOffsetLeftFoot = 0.0f;
IKInterpSpeed = 10.0f;
IKMaxOffset = 55.0f;
HipDisplacementFactor = 1.5f;
RightFootSocket = "foot_rSocket";
LeftFootSocket = "foot_lSocket";
}
// Called when the game starts or when spawned
......@@ -46,119 +34,6 @@ void AVirtualHuman::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Calculate IKOffset for Foot alignment
FVector MeshLocation = GetMesh()->GetComponentLocation();
// RightFoot
FHitResult Hit = IKFootTrace(RightFootSocket, IKTraceDistance);
// the location we want the foot of the VA to be
FVector HitLocationRight = Hit.Location;
FVector offsetRight = HitLocationRight - MeshLocation;
float IKOffsetRight = offsetRight.Z - IKHipOffset;
// LeftFoot
Hit = IKFootTrace(LeftFootSocket, IKTraceDistance);
// the location we want the foot of the VA to be
FVector HitLocationLeft = Hit.Location;
FVector offsetLeft = HitLocationLeft - MeshLocation;
float IKOffsetLeft = offsetLeft.Z - IKHipOffset;
// adjust the height of the VA, by lowering the hip bone
float footHeightDistance = abs(HitLocationRight.Z - HitLocationLeft.Z);
float hipOffset = 0;
// lower the hip for a more natural posture, when walking on slopes or stairs
if (footHeightDistance < 100.0f)
{
hipOffset = footHeightDistance * -0.5f * HipDisplacementFactor;
}
// adjust foot rotation
FVector normal = Hit.ImpactNormal;
FVector up = MeshLocation.UpVector;
float footRotationAngleRAD = acosf(FVector::DotProduct(normal, up));
// check if VA is walking UP or DOWN and change sign of footRotationAngle accordingly
FVector velocity = this->GetVelocity();
float normalToVelocity = FVector::DotProduct(normal, velocity);
if(velocity.Size() > 0) // only change when walking
{
// if angle between normal and velocity is bigger than 90 degree value is negative.
// that means VA is walking UP
if(normalToVelocity < 0)
{
bWalkingUp = true;
} else
{
bWalkingUp = false;
}
}
// the rotation introduces a height error
// the Socket is at the front of the foot
// the bone we rotate around is at the back of the foot
FVector footVector = GetMesh()->GetBoneLocation("foot_r") - GetMesh()->GetSocketLocation(RightFootSocket);
float footLength = footVector.Size(); // the length of the shoe
float rotationErrorHeightAdjustment = tanf(footRotationAngleRAD) * footLength;
rotationErrorHeightAdjustment = FMath::Clamp(rotationErrorHeightAdjustment, 0.0f, footLength); // safety clamp
// we always use FInterpTo function to get smooth animations for all variables that we need in the Animation Blueprint
if (bWalkingUp)
{
IKFootRotationAngle = FMath::FInterpTo(IKFootRotationAngle, -FMath::RadiansToDegrees(footRotationAngleRAD), DeltaTime, IKInterpSpeed);
IKOffsetRight -= rotationErrorHeightAdjustment;
IKOffsetLeft -= rotationErrorHeightAdjustment;
}
else
{
IKFootRotationAngle = FMath::FInterpTo(IKFootRotationAngle, FMath::RadiansToDegrees(footRotationAngleRAD), DeltaTime, IKInterpSpeed);
IKOffsetRight += rotationErrorHeightAdjustment;
IKOffsetLeft += rotationErrorHeightAdjustment;
}
// if one of the offsets is still negative we have to lower the hip even further to reach the floor
if (IKOffsetLeft < 0.0f && IKOffsetLeft > -(IKMaxOffset/2.0f))
{
hipOffset += IKOffsetLeft;
}
else if (IKOffsetRight < 0.0f && IKOffsetRight > -(IKMaxOffset / 2.0f))
{
hipOffset += IKOffsetRight;
}
hipOffset = FMath::Clamp(hipOffset, -IKMaxOffset, 0.0f);
IKHipOffset = FMath::FInterpTo(IKHipOffset, hipOffset, DeltaTime, IKInterpSpeed);
IKOffsetRight = FMath::Clamp(IKOffsetRight, 0.0f, IKMaxOffset);
IKOffsetRigthFoot = FMath::FInterpTo(IKOffsetRigthFoot, IKOffsetRight, DeltaTime, IKInterpSpeed);
IKOffsetLeft = FMath::Clamp(IKOffsetLeft, 0.0f, IKMaxOffset);
IKOffsetLeftFoot = FMath::FInterpTo(IKOffsetLeftFoot, IKOffsetLeft, DeltaTime, IKInterpSpeed);
// write variables to Animation Instance
if(AnimationInstance)
{
AnimationInstance->IKOffsetRight = -IKOffsetRigthFoot;
AnimationInstance->IKOffsetLeft = IKOffsetLeftFoot;
AnimationInstance->IKHipOffset = IKHipOffset;
AnimationInstance->IKFootRotation = IKFootRotationAngle;
}
}
// Called to bind functionality to input
......@@ -168,26 +43,3 @@ void AVirtualHuman::SetupPlayerInputComponent(UInputComponent* PlayerInputCompon
}
FHitResult AVirtualHuman::IKFootTrace(FName Socket, float TraceDistance)
{
USkeletalMeshComponent* mesh = GetMesh();
FVector SocketLocation = mesh->GetSocketLocation(Socket);
FVector ActorLocation = GetActorLocation();
// line trace start location
FVector LineTraceStart = SocketLocation;
LineTraceStart.Z = ActorLocation.Z;
// line trace end location
FVector LineTraceEnd = SocketLocation;
LineTraceEnd.Z = SocketLocation.Z - IKTraceDistance;
FHitResult Hit;
FCollisionQueryParams TraceParams;
TraceParams.bTraceComplex = true;
TraceParams.AddIgnoredActor(this);
bool hitFound = GetWorld()->LineTraceSingleByChannel(Hit, LineTraceStart, LineTraceEnd, ECC_Visibility, TraceParams);
// DrawDebugLine(GetWorld(), LineTraceStart, LineTraceEnd, FColor::Cyan, false, 0.1f);
return Hit;
}
......@@ -82,6 +82,39 @@ public:
UCharacterMovementComponent* GetCharacterMovementComponent() const;
/*
* Specify to which WaypointGroup this VA belongs to.
* VAs will walk to waypoints, that are in the same WaypointGroup
*/
UPROPERTY(EditAnywhere, meta = (AllowPrivateAccess = "true"), Category = WaypointMovement)
int WaypointGroup = 0;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKInterpSpeed;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKOffsetRigthFoot;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKOffsetLeftFoot;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKMaxOffset;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKHipOffset;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float HipDisplacementFactor;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
FName RightFootSocket;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
FName LeftFootSocket;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKFootRotationAngle;
protected:
// Called when the game starts
virtual void BeginPlay() override;
......@@ -100,11 +133,20 @@ private:
APawn* AVirtualHumanPawn;
AVirtualHuman* parentVH;
AVirtualHuman* AVirtualHumanPtr;
AVirtualHumanAIController* VirtualHumanAIController;
AWaypoint* ACurrentWaypoint;
UNavigationPath* NavigationPath;
float IKTraceDistance;
bool bWalkingUp;
FVector HitLocation;
UVHAnimInstance* AnimationInstance;
FHitResult IKFootTrace(FName Socket, float TraceDistance);
void CreateChildPoints();
TArray<FVector> CreateIntermediatePoints(AWaypoint* last, AWaypoint* current, AWaypoint* next);
float CalculateChildSpeed(AWaypoint* nextWaypoint, TArray<FVector> childPoints);
......@@ -115,16 +157,6 @@ private:
UPROPERTY(meta = (AllowPrivateAccess = "true"))
int CurrentWaypointIterator;
public:
/*
* Specify to which WaypointGroup this VA belongs to.
* VAs will walk to waypoints, that are in the same WaypointGroup
*/
UPROPERTY(EditAnywhere, meta = (AllowPrivateAccess = "true"), Category = WaypointMovement)
int WaypointGroup = 0;
private:
// loop to the first waypoint after reaching the last
UPROPERTY(EditAnywhere, Category = WaypointMovement)
bool bLoop = false;
......@@ -148,4 +180,5 @@ private:
UFUNCTION(BlueprintCallable)
void WaypointMovement();
};
......@@ -24,41 +24,11 @@ public:
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKInterpSpeed;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKOffsetRigthFoot;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKOffsetLeftFoot;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKMaxOffset;
UPROPERTY(BlueprintReadOnly, Category = IKFoot)
float IKHipOffset;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float HipDisplacementFactor;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
FName RightFootSocket;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
FName LeftFootSocket;
UPROPERTY(BlueprintReadWrite, Category = IKFoot)
float IKFootRotationAngle;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
UVHAnimInstance* AnimationInstance;
float IKTraceDistance;
bool bWalkingUp;
FVector HitLocation;
FHitResult IKFootTrace(FName Socket, float TraceDistance);
};
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment