diff --git a/Source/RWTHVRToolkit/Private/Pawn/VRPawnMovement.cpp b/Source/RWTHVRToolkit/Private/Pawn/VRPawnMovement.cpp index ca8cdb48addeb1c2c980c174d6ea413827d5e9f6..76a167405e9475d44a19b9d75c0e4750a20038ef 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/VRPawnMovement.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/VRPawnMovement.cpp @@ -10,37 +10,56 @@ UVRPawnMovement::UVRPawnMovement(const FObjectInitializer& ObjectInitializer) : CapsuleColliderComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); CapsuleColliderComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, 80.0f); + + //set some defaults for the UFloatingPawnMovement component, which are more reasonable for usage in VR + MaxSpeed = 300.f; + Acceleration = 800.f; + Deceleration = 2000.f; +} + +void UVRPawnMovement::BeginPlay() +{ + Super::BeginPlay(); + LastCapsulePosition=FVector(0,0,0); + LastSteeringCollisionVector = FVector(0,0,0); } + void UVRPawnMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction){ SetCapsuleColliderToUserSize(); - FVector PositionChange = GetPendingInputVector(); + FVector InputVector = GetPendingInputVector(); if (NavigationMode == EVRNavigationModes::NAV_WALK) { // you are only allowed to move horizontally in NAV_WALK // everything else will be handled by stepping-up/gravity // so remove Z component for the input vector of the UFloatingPawnMovement - PositionChange.Z = 0.0f; + InputVector.Z = 0.0f; ConsumeInputVector(); - AddInputVector(PositionChange); + AddInputVector(InputVector); } if(NavigationMode == EVRNavigationModes::NAV_FLY || NavigationMode == EVRNavigationModes::NAV_WALK) { - CheckForPhysWalkingCollision(); - if(CheckForVirtualSteerCollision(PositionChange, DeltaTime)) - { - // if we would move into something if we apply this input (estimating distance by max speed) - // we consume the input so it is not applied - ConsumeInputVector(); + if(InputVector.Size()>0.001){ + const FVector SafeSteeringInput = GetCollisionSafeVirtualSteeringVec(InputVector, DeltaTime); + if(SafeSteeringInput != InputVector) + { + // if we would move into something if we apply this input (estimating distance by max speed) + // we only apply its perpendicular part (unless it is facing away from the collision) + ConsumeInputVector(); + AddInputVector(SafeSteeringInput); + } } // so we add stepping-up (for both walk and fly) // and gravity for walking only MoveByGravityOrStepUp(DeltaTime); + + //if we physically (in the tracking space) walked into something, move the world away (by moving the pawn) + CheckForPhysWalkingCollision(); } if(NavigationMode == EVRNavigationModes::NAV_NONE) @@ -50,8 +69,6 @@ void UVRPawnMovement::TickComponent(float DeltaTime, enum ELevelTick TickType, F } Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - - LastHeadPosition = HeadComponent->GetComponentLocation(); } void UVRPawnMovement::SetHeadComponent(USceneComponent* NewHeadComponent) @@ -90,54 +107,98 @@ void UVRPawnMovement::SetCapsuleColliderToUserSize() const float ColliderHeight = UserSize - MaxStepHeight; const float ColliderHalfHeight = ColliderHeight / 2.0f; if (ColliderHalfHeight <= CapsuleRadius) - {//Make the collider to a Sphere + { + //the capsule will actually be compressed to a sphere CapsuleColliderComponent->SetCapsuleSize(ColliderHalfHeight, ColliderHalfHeight); } else - {//Make the collider to a Capsule + { CapsuleColliderComponent->SetCapsuleSize(CapsuleRadius, ColliderHalfHeight); } - CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation()); - CapsuleColliderComponent->AddWorldOffset(FVector(0, 0, -ColliderHalfHeight)); + CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation() - FVector(0, 0, ColliderHalfHeight)); } else { CapsuleColliderComponent->SetWorldLocation(HeadComponent->GetComponentLocation()); } + CapsuleColliderComponent->SetWorldRotation(FRotator::ZeroRotator); } void UVRPawnMovement::CheckForPhysWalkingCollision() { - const FHitResult HitResult = CreateCapsuleTrace(LastHeadPosition, HeadComponent->GetComponentLocation(), false); + if(LastCapsulePosition.IsNearlyZero()) + { + //probably not yet set, so do nothing than setting it + LastCapsulePosition = GetCapsuleLocationFromHead(); + return; + } - //if this was not possible move it the entire pawn to avoid the head collision + const FVector CapsuleLocation = GetCapsuleLocationFromHead(); + const FHitResult HitResult = CreateCapsuleTrace(LastCapsulePosition, CapsuleLocation); + + //if this was not possible move the entire pawn away to avoid the head collision if (HitResult.bBlockingHit) { - UpdatedComponent->AddLocalOffset(HitResult.Normal*HitResult.PenetrationDepth); + const FVector MoveOutVector = HitResult.Location-CapsuleLocation; + UpdatedComponent->AddWorldOffset(MoveOutVector); + } + else + { + //only update if not in collision + if(!CreateCapsuleTrace(CapsuleLocation, CapsuleLocation).bBlockingHit) + { + LastCapsulePosition = CapsuleLocation; + } } } -bool UVRPawnMovement::CheckForVirtualSteerCollision(FVector PositionChange, float DeltaTime) +FVector UVRPawnMovement::GetCollisionSafeVirtualSteeringVec(FVector InputVector, float DeltaTime) { - FVector ProbePosition = PositionChange.GetSafeNormal() * GetMaxSpeed() * DeltaTime; - const FHitResult HitResult = CreateCapsuleTrace(HeadComponent->GetComponentLocation(), ProbePosition, false); - if (HitResult.bBlockingHit) + + const float SafetyFactor = 3.0f; //so we detect collision a bit earlier + const FVector CapsuleLocation = GetCapsuleLocationFromHead(); + FVector ProbePosition = SafetyFactor * InputVector.GetSafeNormal() * GetMaxSpeed() * DeltaTime + CapsuleLocation; + const FHitResult TraceResult = CreateCapsuleTrace(CapsuleLocation, ProbePosition); + if (!TraceResult.bBlockingHit) { - return true; + //everything is fine, use that vector + return InputVector; } - return false; + + //otherwise remove the component of that vector that goes towards the collision + FVector CollisionVector = TraceResult.Location - CapsuleLocation; + + //sometimes (if by chance we already moved into collision entirely CollisionVector is 0 + if(! CollisionVector.Normalize()) + { + //then we probably start already in collision, so we use the last one + CollisionVector = LastSteeringCollisionVector; + } + else + { + LastSteeringCollisionVector = CollisionVector; + } + + FVector SafeInput = InputVector; + const float DotProduct = FVector::DotProduct(InputVector, CollisionVector); + if(DotProduct>0.0f) + { + // only keep perpendicular part of the input vector (remove anything towards hit) + SafeInput -= DotProduct * CollisionVector; + } + return SafeInput; } void UVRPawnMovement::MoveByGravityOrStepUp(float DeltaSeconds) { const FVector DownTraceStart = CapsuleColliderComponent->GetComponentLocation(); - const float DownTraceDist = 1000.0f; + const float DownTraceDist = MaxFallingDepth < 0.0f ? 1000.0f : MaxFallingDepth; const FVector DownTraceDir = FVector(0,0,-1); const FVector DownTraceEnd = DownTraceStart + DownTraceDist * DownTraceDir; - const FHitResult DownTraceHitResult = CreateCapsuleTrace(DownTraceStart, DownTraceEnd, true); + const FHitResult DownTraceHitResult = CreateCapsuleTrace(DownTraceStart, DownTraceEnd); float HeightDifference = 0.0f; if(DownTraceHitResult.bBlockingHit) @@ -145,6 +206,7 @@ void UVRPawnMovement::MoveByGravityOrStepUp(float DeltaSeconds) HeightDifference = DownTraceHitResult.ImpactPoint.Z - UpdatedComponent->GetComponentLocation().Z; //so for HeightDifference>0, we have to move the pawn up; for HeightDifference<0 we have to move it down } + //Going up (in Fly and Walk Mode) if (HeightDifference>0.0f && HeightDifference<=MaxStepHeight) { @@ -156,8 +218,13 @@ void UVRPawnMovement::MoveByGravityOrStepUp(float DeltaSeconds) return; } + if(!DownTraceHitResult.bBlockingHit && MaxFallingDepth<0.0f) + { + HeightDifference = -1000.0f; //just fall + } + //Gravity (only in Walk Mode) - if (!DownTraceHitResult.bBlockingHit || HeightDifference<0.0f) + if (HeightDifference<0.0f) { ShiftVertically(HeightDifference, GravityAcceleration, DeltaSeconds); } @@ -168,20 +235,29 @@ void UVRPawnMovement::ShiftVertically(float Distance, float VerticalAcceleration VerticalSpeed += VerticalAcceleration * DeltaSeconds; if (abs(VerticalSpeed*DeltaSeconds) < abs(Distance)) { - UpdatedComponent->AddLocalOffset(FVector(0.f, 0.f, VerticalSpeed * DeltaSeconds)); + UpdatedComponent->AddWorldOffset(FVector(0.f, 0.f, VerticalSpeed * DeltaSeconds)); } else { - UpdatedComponent->AddLocalOffset(FVector(0.f, 0.f, Distance)); + UpdatedComponent->AddWorldOffset(FVector(0.f, 0.f, Distance)); VerticalSpeed = 0; } } +FVector UVRPawnMovement::GetCapsuleLocationFromHead() +{ + return HeadComponent->GetComponentLocation() - FVector(0, 0, CapsuleColliderComponent->GetScaledCapsuleHalfHeight()); +} + FHitResult UVRPawnMovement::CreateCapsuleTrace(const FVector Start, FVector End, bool DrawDebug) const { const EDrawDebugTrace::Type DrawType = DrawDebug ? EDrawDebugTrace::Type::ForDuration : EDrawDebugTrace::Type::None; + TArray<AActor*> ActorsToIgnore; + ActorsToIgnore.Add(GetOwner()); + + //UE_LOG(LogTemp, Warning, TEXT("Capsule from %s to %s"), *Start.ToString(), *End.ToString()) FHitResult Hit; - UKismetSystemLibrary::CapsuleTraceSingle(GetWorld(), Start, End, CapsuleColliderComponent->GetScaledCapsuleRadius(), CapsuleColliderComponent->GetScaledCapsuleHalfHeight() ,UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_Visibility), true, {}, DrawType, Hit, true); + UKismetSystemLibrary::CapsuleTraceSingle(GetWorld(), Start, End, CapsuleColliderComponent->GetScaledCapsuleRadius(), CapsuleColliderComponent->GetScaledCapsuleHalfHeight() ,UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_Visibility), true, ActorsToIgnore, DrawType, Hit, true); return Hit; } diff --git a/Source/RWTHVRToolkit/Public/Pawn/VRPawnMovement.h b/Source/RWTHVRToolkit/Public/Pawn/VRPawnMovement.h index 43aeb7827aac1b16a84d4acecae140a0348f3868..13d659bbe26502c3736470608e1f8220c19ea49e 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/VRPawnMovement.h +++ b/Source/RWTHVRToolkit/Public/Pawn/VRPawnMovement.h @@ -35,6 +35,8 @@ class RWTHVRTOOLKIT_API UVRPawnMovement : public UFloatingPawnMovement public: + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; @@ -43,30 +45,38 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") EVRNavigationModes NavigationMode = EVRNavigationModes::NAV_WALK; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement", meta = (ClampMin="0.0")) float MaxStepHeight = 40.0f; + // if the height that the pawn would fall (in walking mode) is higher + // it is not falling, set to <0.0f if you want to fall infinitely UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") + float MaxFallingDepth = 1000.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement", meta = (ClampMax="0.0")) float GravityAcceleration = -981.0f; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") - float UpSteppingAcceleration = 500.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement", meta = (ClampMin="0.0")) + float UpSteppingAcceleration = 981.0f; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VR Movement", meta = (ClampMin="0.0")) float CapsuleRadius = 40.0f; private: //check for - FHitResult CreateCapsuleTrace(const FVector Start, FVector End, bool DrawDebug) const; + FHitResult CreateCapsuleTrace(const FVector Start, FVector End, bool DrawDebug=false) const; void SetCapsuleColliderToUserSize(); void CheckForPhysWalkingCollision(); - bool CheckForVirtualSteerCollision(FVector PositionChange, float DeltaTime); + FVector GetCollisionSafeVirtualSteeringVec(FVector InputVector, float DeltaTime); void MoveByGravityOrStepUp(float DeltaSeconds); void ShiftVertically(float Distance, float VerticalAcceleration, float DeltaSeconds); + FVector GetCapsuleLocationFromHead(); + UPROPERTY(VisibleAnywhere) UCapsuleComponent* CapsuleColliderComponent = nullptr; UPROPERTY() USceneComponent* HeadComponent = nullptr; float VerticalSpeed = 0.0f; - FVector LastHeadPosition; + FVector LastCapsulePosition; + FVector LastSteeringCollisionVector; };