void UMovementComponent::TwoWallAdjust(FVector& OutDelta, const FHitResult& Hit, const FVector& OldHitNormal) const { FVector Delta = OutDelta; const FVector HitNormal = Hit.Normal; if ((OldHitNormal | HitNormal) <= 0.f) //90 or less corner, so use cross product for direction { const FVector DesiredDir = Delta; FVector NewDir = (HitNormal ^ OldHitNormal); NewDir = NewDir.GetSafeNormal(); Delta = (Delta | NewDir) * (1.f - Hit.Time) * NewDir; if ((DesiredDir | Delta) < 0.f) { Delta = -1.f * Delta; } } else //adjust to new wall { const FVector DesiredDir = Delta; Delta = ComputeSlideVector(Delta, 1.f - Hit.Time, HitNormal, Hit); if ((Delta | DesiredDir) <= 0.f) { Delta = FVector::ZeroVector; } else if ( FMath::Abs((HitNormal | OldHitNormal) - 1.f) < KINDA_SMALL_NUMBER ) { // we hit the same wall again even after adjusting to move along it the first time // nudge away from it (this can happen due to precision issues) Delta += HitNormal * 0.01f; } } OutDelta = Delta; }
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; }
float UMovementComponent::SlideAlongSurface(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit, bool bHandleImpact) { if (!Hit.bBlockingHit) { return 0.f; } float PercentTimeApplied = 0.f; const FVector OldHitNormal = Normal; FVector SlideDelta = ComputeSlideVector(Delta, Time, Normal, Hit); if ((SlideDelta | Delta) > 0.f) { const FQuat Rotation = UpdatedComponent->GetComponentQuat(); SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit); const float FirstHitPercent = Hit.Time; PercentTimeApplied = FirstHitPercent; if (Hit.IsValidBlockingHit()) { // Notify first impact if (bHandleImpact) { HandleImpact(Hit, FirstHitPercent * Time, SlideDelta); } // Compute new slide normal when hitting multiple surfaces. TwoWallAdjust(SlideDelta, Hit, OldHitNormal); // Only proceed if the new direction is of significant length and not in reverse of original attempted move. if (!SlideDelta.IsNearlyZero(1e-3f) && (SlideDelta | Delta) > 0.f) { // Perform second move SafeMoveUpdatedComponent(SlideDelta, Rotation, true, Hit); const float SecondHitPercent = Hit.Time * (1.f - FirstHitPercent); PercentTimeApplied += SecondHitPercent; // Notify second impact if (bHandleImpact && Hit.bBlockingHit) { HandleImpact(Hit, SecondHitPercent * Time, SlideDelta); } } } return FMath::Clamp(PercentTimeApplied, 0.f, 1.f); } return 0.f; }