bool UProjectileMovementComponent::HandleSliding(FHitResult& Hit, float& SubTickTimeRemaining) { FHitResult InitialHit(Hit); const FVector OldHitNormal = ConstrainDirectionToPlane(Hit.Normal); // Velocity is now parallel to the impact surface. // Perform the move now, before adding gravity/accel again, so we don't just keep hitting the surface. SafeMoveUpdatedComponent(Velocity * SubTickTimeRemaining, UpdatedComponent->GetComponentQuat(), true, Hit); if (HasStoppedSimulation()) { return false; } // A second hit can deflect the velocity (through the normal bounce code), for the next iteration. if (Hit.bBlockingHit) { const float TimeTick = SubTickTimeRemaining; SubTickTimeRemaining = TimeTick * (1.f - Hit.Time); if (HandleBlockingHit(Hit, TimeTick, Velocity * TimeTick, SubTickTimeRemaining) == EHandleBlockingHitResult::Abort || HasStoppedSimulation()) { return false; } } else { // Find velocity after elapsed time const FVector PostTickVelocity = ComputeVelocity(Velocity, SubTickTimeRemaining); // If pointing back into surface, apply friction and acceleration. const FVector Force = (PostTickVelocity - Velocity); const float ForceDotN = (Force | OldHitNormal); if (ForceDotN < 0.f) { const FVector ProjectedForce = FVector::VectorPlaneProject(Force, OldHitNormal); const FVector NewVelocity = Velocity + ProjectedForce; const FVector FrictionForce = -NewVelocity.GetSafeNormal() * FMath::Min(-ForceDotN * Friction, NewVelocity.Size()); Velocity = ConstrainDirectionToPlane(NewVelocity + FrictionForce); } else { Velocity = PostTickVelocity; } // Check min velocity if (Velocity.SizeSquared() < FMath::Square(BounceVelocityStopSimulatingThreshold)) { StopSimulating(InitialHit); return false; } SubTickTimeRemaining = 0.f; } return true; }
FVector UProjectileMovementComponent::LimitVelocity(FVector NewVelocity) const { const float CurrentMaxSpeed = GetMaxSpeed(); if (CurrentMaxSpeed > 0.f) { NewVelocity = NewVelocity.GetClampedToMaxSize(CurrentMaxSpeed); } return ConstrainDirectionToPlane(NewVelocity); }
bool UMovementComponent::MoveUpdatedComponentImpl( const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit, ETeleportType Teleport) { if (UpdatedComponent) { const FVector NewDelta = ConstrainDirectionToPlane(Delta); return UpdatedComponent->MoveComponent(NewDelta, NewRotation, bSweep, OutHit, MoveComponentFlags, Teleport); } return false; }
bool UProjectileMovementComponent::HandleDeflection(FHitResult& Hit, const FVector& OldVelocity, const uint32 NumBounces, float& SubTickTimeRemaining) { const FVector Normal = ConstrainNormalToPlane(Hit.Normal); // Multiple hits within very short time period? const bool bMultiHit = (PreviousHitTime < 1.f && Hit.Time <= KINDA_SMALL_NUMBER); // if velocity still into wall (after HandleBlockingHit() had a chance to adjust), slide along wall const float DotTolerance = 0.01f; bIsSliding = (bMultiHit && FVector::Coincident(PreviousHitNormal, Normal)) || ((Velocity.GetSafeNormal() | Normal) <= DotTolerance); if (bIsSliding) { if (bMultiHit && (PreviousHitNormal | Normal) <= 0.f) { //90 degree or less corner, so use cross product for direction FVector NewDir = (Normal ^ PreviousHitNormal); NewDir = NewDir.GetSafeNormal(); Velocity = Velocity.ProjectOnToNormal(NewDir); if ((OldVelocity | Velocity) < 0.f) { Velocity *= -1.f; } Velocity = ConstrainDirectionToPlane(Velocity); } else { //adjust to move along new wall Velocity = ComputeSlideVector(Velocity, 1.f, Normal, Hit); } // Check min velocity. if (Velocity.SizeSquared() < FMath::Square(BounceVelocityStopSimulatingThreshold)) { StopSimulating(Hit); return false; } // Velocity is now parallel to the impact surface. if (SubTickTimeRemaining > KINDA_SMALL_NUMBER) { if (!HandleSliding(Hit, SubTickTimeRemaining)) { return false; } } } return true; }
FVector UMovementComponent::GetPenetrationAdjustment(const FHitResult& Hit) const { if (!Hit.bStartPenetrating) { return FVector::ZeroVector; } FVector Result; const float PullBackDistance = FMath::Abs(CVarPenetrationPullbackDistance.GetValueOnGameThread()); const float PenetrationDepth = (Hit.PenetrationDepth > 0.f ? Hit.PenetrationDepth : 0.125f); Result = Hit.Normal * (PenetrationDepth + PullBackDistance); return ConstrainDirectionToPlane(Result); }
bool UMovementComponent::ResolvePenetrationImpl(const FVector& ProposedAdjustment, const FHitResult& Hit, const FQuat& NewRotationQuat) { // SceneComponent can't be in penetration, so this function really only applies to PrimitiveComponent. const FVector Adjustment = ConstrainDirectionToPlane(ProposedAdjustment); if (!Adjustment.IsZero() && UpdatedPrimitive) { // See if we can fit at the adjusted location without overlapping anything. AActor* ActorOwner = UpdatedComponent->GetOwner(); if (!ActorOwner) { return false; } UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: %s.%s at location %s inside %s.%s at location %s by %.3f (netmode: %d)"), *ActorOwner->GetName(), *UpdatedComponent->GetName(), *UpdatedComponent->GetComponentLocation().ToString(), *GetNameSafe(Hit.GetActor()), *GetNameSafe(Hit.GetComponent()), Hit.Component.IsValid() ? *Hit.GetComponent()->GetComponentLocation().ToString() : TEXT("<unknown>"), Hit.PenetrationDepth, (uint32)GetNetMode()); // We really want to make sure that precision differences or differences between the overlap test and sweep tests don't put us into another overlap, // so make the overlap test a bit more restrictive. const float OverlapInflation = CVarPenetrationOverlapCheckInflation.GetValueOnGameThread(); bool bEncroached = OverlapTest(Hit.TraceStart + Adjustment, NewRotationQuat, UpdatedPrimitive->GetCollisionObjectType(), UpdatedPrimitive->GetCollisionShape(OverlapInflation), ActorOwner); if (!bEncroached) { // Move without sweeping. MoveUpdatedComponent(Adjustment, NewRotationQuat, false, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: teleport by %s"), *Adjustment.ToString()); return true; } else { // Disable MOVECOMP_NeverIgnoreBlockingOverlaps if it is enabled, otherwise we wouldn't be able to sweep out of the object to fix the penetration. TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, EMoveComponentFlags(MoveComponentFlags & (~MOVECOMP_NeverIgnoreBlockingOverlaps))); // Try sweeping as far as possible... FHitResult SweepOutHit(1.f); bool bMoved = MoveUpdatedComponent(Adjustment, NewRotationQuat, true, &SweepOutHit, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (success = %d)"), *Adjustment.ToString(), bMoved); // Still stuck? if (!bMoved && SweepOutHit.bStartPenetrating) { // Combine two MTD results to get a new direction that gets out of multiple surfaces. const FVector SecondMTD = GetPenetrationAdjustment(SweepOutHit); const FVector CombinedMTD = Adjustment + SecondMTD; if (SecondMTD != Adjustment && !CombinedMTD.IsZero()) { bMoved = MoveUpdatedComponent(CombinedMTD, NewRotationQuat, true, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (MTD combo success = %d)"), *CombinedMTD.ToString(), bMoved); } } // Still stuck? if (!bMoved) { // Try moving the proposed adjustment plus the attempted move direction. This can sometimes get out of penetrations with multiple objects const FVector MoveDelta = ConstrainDirectionToPlane(Hit.TraceEnd - Hit.TraceStart); if (!MoveDelta.IsZero()) { bMoved = MoveUpdatedComponent(Adjustment + MoveDelta, NewRotationQuat, true, nullptr, ETeleportType::TeleportPhysics); UE_LOG(LogMovement, Verbose, TEXT("ResolvePenetration: sweep by %s (adjusted attempt success = %d)"), *(Adjustment + MoveDelta).ToString(), bMoved); } } return bMoved; } } return false; }