A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
VRCharacterMovementComponent.cpp
Go to the documentation of this file.
1// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 Movement.cpp: Character movement implementation
5
6=============================================================================*/
7
9#include "GameFramework/PhysicsVolume.h"
10#include "GameFramework/GameNetworkManager.h"
11#include "GameFramework/Character.h"
12#include "GameFramework/GameState.h"
13#include "Components/PrimitiveComponent.h"
14#include "Animation/AnimMontage.h"
15#include "DrawDebugHelpers.h"
16//#include "PhysicsEngine/DestructibleActor.h"
17#include "VRCharacter.h"
19
20// @todo this is here only due to circular dependency to AIModule. To be removed
21#include "Navigation/PathFollowingComponent.h"
22#include "AI/Navigation/AvoidanceManager.h"
23#include "Components/CapsuleComponent.h"
24#include "Components/BrushComponent.h"
25//#include "Components/DestructibleComponent.h"
26
27#include "Engine/DemoNetDriver.h"
28#include "Engine/NetworkObjectList.h"
29
30//#include "PerfCountersHelpers.h"
31
32DEFINE_LOG_CATEGORY(LogVRCharacterMovement);
33
37DECLARE_CYCLE_STAT(TEXT("Char StepUp"), STAT_CharStepUp, STATGROUP_Character);
38DECLARE_CYCLE_STAT(TEXT("Char FindFloor"), STAT_CharFindFloor, STATGROUP_Character);
39DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServer"), STAT_CharacterMovementReplicateMoveToServer, STATGROUP_Character);
40DECLARE_CYCLE_STAT(TEXT("Char CallServerMove"), STAT_CharacterMovementCallServerMove, STATGROUP_Character);
41DECLARE_CYCLE_STAT(TEXT("Char CombineNetMove"), STAT_CharacterMovementCombineNetMove, STATGROUP_Character);
42DECLARE_CYCLE_STAT(TEXT("Char PhysWalking"), STAT_CharPhysWalking, STATGROUP_Character);
43DECLARE_CYCLE_STAT(TEXT("Char PhysFalling"), STAT_CharPhysFalling, STATGROUP_Character);
44DECLARE_CYCLE_STAT(TEXT("Char PhysNavWalking"), STAT_CharPhysNavWalking, STATGROUP_Character);
45DECLARE_CYCLE_STAT(TEXT("Char NavProjectPoint"), STAT_CharNavProjectPoint, STATGROUP_Character);
46DECLARE_CYCLE_STAT(TEXT("Char NavProjectLocation"), STAT_CharNavProjectLocation, STATGROUP_Character);
47DECLARE_CYCLE_STAT(TEXT("Char AdjustFloorHeight"), STAT_CharAdjustFloorHeight, STATGROUP_Character);
48DECLARE_CYCLE_STAT(TEXT("Char ProcessLanded"), STAT_CharProcessLanded, STATGROUP_Character);
49
50// MAGIC NUMBERS
51const float MAX_STEP_SIDE_Z = 0.08f; // maximum z value for the normal on the vertical side of steps
52const float SWIMBOBSPEED = -80.f;
53const float VERTICAL_SLOPE_NORMAL_Z = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface.
54
55// Defines for build configs
56#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled.
57#define devCodeVR( Code ) checkCode( Code )
58#else
59#define devCodeVR(...)
60#endif
61
62// Statics
64{
65 static const FName CrouchTraceName = FName(TEXT("CrouchTrace"));
66 static const FName ImmersionDepthName = FName(TEXT("MovementComp_Character_ImmersionDepth"));
67
68 static float fRotationCorrectionThreshold = 0.02f;
69 FAutoConsoleVariableRef CVarRotationCorrectionThreshold(
70 TEXT("vre.RotationCorrectionThreshold"),
72 TEXT("Error threshold value before correcting a clients rotation.\n")
73 TEXT("Rotation is replicated at 2 decimal precision, so values less than 0.01 won't matter."),
74 ECVF_Default);
75
76}
77
88
89void UVRCharacterMovementComponent::Crouch(bool bClientSimulation)
90{
91 if (!HasValidData())
92 {
93 return;
94 }
95
96 if (!bClientSimulation && !CanCrouchInCurrentState())
97 {
98 return;
99 }
100
101 // See if collision is already at desired size.
102 if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == CrouchedHalfHeight)
103 {
104 if (!bClientSimulation)
105 {
106 CharacterOwner->bIsCrouched = true;
107 }
108 CharacterOwner->OnStartCrouch(0.f, 0.f);
109 return;
110 }
111
112 if (bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)
113 {
114 // restore collision size before crouching
115 ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
116 if (VRRootCapsule)
117 VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight());
118 else
119 CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight());
120
121 bShrinkProxyCapsule = true;
122 }
123
124 // Change collision size to crouching dimensions
125 const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale();
126 const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight();
127 const float OldUnscaledRadius = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleRadius();
128 // Height is not allowed to be smaller than radius.
129 const float ClampedCrouchedHalfHeight = FMath::Max3(0.f, OldUnscaledRadius, CrouchedHalfHeight);
130
131 if (VRRootCapsule)
132 VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, ClampedCrouchedHalfHeight);
133 else
134 CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, ClampedCrouchedHalfHeight);
135
136 float HalfHeightAdjust = (OldUnscaledHalfHeight - ClampedCrouchedHalfHeight);
137 float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale;
138
139 if (!bClientSimulation)
140 {
141 // Crouching to a larger height? (this is rare)
142 if (ClampedCrouchedHalfHeight > OldUnscaledHalfHeight)
143 {
144 FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner);
145 FCollisionResponseParams ResponseParam;
146 InitCollisionParams(CapsuleParams, ResponseParam);
147
148 FVector capLocation;
149 if (VRRootCapsule)
150 {
151 capLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
152 }
153 else
154 capLocation = UpdatedComponent->GetComponentLocation();
155
156 const bool bEncroached = GetWorld()->OverlapBlockingTestByChannel(capLocation - FVector(0.f, 0.f, ScaledHalfHeightAdjust), FQuat::Identity,
157 UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CapsuleParams, ResponseParam);
158
159 // If encroached, cancel
160 if (bEncroached)
161 {
162 if (VRRootCapsule)
163 VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, OldUnscaledHalfHeight);
164 else
165 CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, OldUnscaledHalfHeight);
166 return;
167 }
168 }
169
170 // Skipping this move down as the VR character's base root doesn't behave the same
171 if (bCrouchMaintainsBaseLocation)
172 {
173 // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot.
174 //UpdatedComponent->MoveComponent(FVector(0.f, 0.f, -ScaledHalfHeightAdjust), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics);
175 }
176
177 CharacterOwner->bIsCrouched = true;
178 }
179
180 bForceNextFloorCheck = true;
181
182 // OnStartCrouch takes the change from the Default size, not the current one (though they are usually the same).
183 const float MeshAdjust = ScaledHalfHeightAdjust;
184 ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
185 HalfHeightAdjust = (DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - ClampedCrouchedHalfHeight);
186 ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale;
187
188 AdjustProxyCapsuleSize();
189 CharacterOwner->OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
190
191 // Don't smooth this change in mesh position
192 if (/*(bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) ||*/ (IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy))
193 {
194 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
195
196 if (ClientData)
197 {
198 ClientData->MeshTranslationOffset -= FVector(0.f, 0.f, MeshAdjust);
199 ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset;
200 }
201 }
202}
203
204void UVRCharacterMovementComponent::UnCrouch(bool bClientSimulation)
205{
206 if (!HasValidData())
207 {
208 return;
209 }
210
211 ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>();
212
213 // See if collision is already at desired size.
214 if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight())
215 {
216 if (!bClientSimulation)
217 {
218 CharacterOwner->bIsCrouched = false;
219 }
220 CharacterOwner->OnEndCrouch(0.f, 0.f);
221 return;
222 }
223
224 const float CurrentCrouchedHalfHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
225
226 const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale();
227 const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight();
228 const float HalfHeightAdjust = DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - OldUnscaledHalfHeight;
229 const float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale;
230
231 /*const*/ FVector PawnLocation = UpdatedComponent->GetComponentLocation();
232
233 if (VRRootCapsule)
234 {
235 PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
236 }
237
238 // Grow to uncrouched size.
239 check(CharacterOwner->GetCapsuleComponent());
240
241 if (!bClientSimulation)
242 {
243 // Try to stay in place and see if the larger capsule fits. We use a slightly taller capsule to avoid penetration.
244 const UWorld* MyWorld = GetWorld();
245 const float SweepInflation = KINDA_SMALL_NUMBER * 10.f;
246 FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner);
247 FCollisionResponseParams ResponseParam;
248 InitCollisionParams(CapsuleParams, ResponseParam);
249
250 // Compensate for the difference between current capsule size and standing size
251 const FCollisionShape StandingCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, -SweepInflation - ScaledHalfHeightAdjust); // Shrink by negative amount, so actually grow it.
252 const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType();
253 bool bEncroached = true;
254
255 if (!bCrouchMaintainsBaseLocation)
256 {
257 // Expand in place
258 bEncroached = MyWorld->OverlapBlockingTestByChannel(PawnLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam);
259
260 if (bEncroached)
261 {
262 // Try adjusting capsule position to see if we can avoid encroachment.
263 if (ScaledHalfHeightAdjust > 0.f)
264 {
265 // Shrink to a short capsule, sweep down to base to find where that would hit something, and then try to stand up from there.
266 float PawnRadius, PawnHalfHeight;
267 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
268 const float ShrinkHalfHeight = PawnHalfHeight - PawnRadius;
269 const float TraceDist = PawnHalfHeight - ShrinkHalfHeight;
270 const FVector Down = FVector(0.f, 0.f, -TraceDist);
271
272 FHitResult Hit(1.f);
273 const FCollisionShape ShortCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, ShrinkHalfHeight);
274 const bool bBlockingHit = MyWorld->SweepSingleByChannel(Hit, PawnLocation, PawnLocation + Down, FQuat::Identity, CollisionChannel, ShortCapsuleShape, CapsuleParams);
275 if (Hit.bStartPenetrating)
276 {
277 bEncroached = true;
278 }
279 else
280 {
281 // Compute where the base of the sweep ended up, and see if we can stand there
282 const float DistanceToBase = (Hit.Time * TraceDist) + ShortCapsuleShape.Capsule.HalfHeight;
283 const FVector NewLoc = FVector(PawnLocation.X, PawnLocation.Y, PawnLocation.Z - DistanceToBase + StandingCapsuleShape.Capsule.HalfHeight + SweepInflation + MIN_FLOOR_DIST / 2.f);
284 bEncroached = MyWorld->OverlapBlockingTestByChannel(NewLoc, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam);
285 if (!bEncroached)
286 {
287 // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot.
288 UpdatedComponent->MoveComponent(NewLoc - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics);
289 }
290 }
291 }
292 }
293 }
294 else
295 {
296 // Expand while keeping base location the same.
297 FVector StandingLocation = PawnLocation + FVector(0.f, 0.f, StandingCapsuleShape.GetCapsuleHalfHeight() - CurrentCrouchedHalfHeight);
298 bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam);
299
300 if (bEncroached)
301 {
302 if (IsMovingOnGround())
303 {
304 // Something might be just barely overhead, try moving down closer to the floor to avoid it.
305 const float MinFloorDist = KINDA_SMALL_NUMBER * 10.f;
306 if (CurrentFloor.bBlockingHit && CurrentFloor.FloorDist > MinFloorDist)
307 {
308 StandingLocation.Z -= CurrentFloor.FloorDist - MinFloorDist;
309 bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam);
310 }
311 }
312 }
313
314 // Canceling this move because our VR capsule isn't actor based like it expects it to be
315 if (!bEncroached)
316 {
317 // Commit the change in location.
318 //UpdatedComponent->MoveComponent(StandingLocation - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics);
319 bForceNextFloorCheck = true;
320 }
321 }
322
323 // If still encroached then abort.
324 if (bEncroached)
325 {
326 return;
327 }
328
329 CharacterOwner->bIsCrouched = false;
330 }
331 else
332 {
333 bShrinkProxyCapsule = true;
334 }
335
336 // Now call SetCapsuleSize() to cause touch/untouch events and actually grow the capsule
337 if (VRRootCapsule)
338 VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true);
339 else
340 CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true);
341
342 const float MeshAdjust = ScaledHalfHeightAdjust;
343 AdjustProxyCapsuleSize();
344 CharacterOwner->OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);
345
346 // Don't smooth this change in mesh position
347 if (/*(bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) || */(IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy))
348 {
349 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
350
351 if (ClientData)
352 {
353 ClientData->MeshTranslationOffset += FVector(0.f, 0.f, MeshAdjust);
354 ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset;
355 }
356 }
357}
358
360{
361 // Should only be called on client or listen server (for remote clients) in network games
362 check(CharacterOwner != NULL);
363 checkSlow(CharacterOwner->GetLocalRole() < ROLE_Authority || (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && GetNetMode() == NM_ListenServer));
364 checkSlow(GetNetMode() == NM_Client || GetNetMode() == NM_ListenServer);
365
366 if (!ClientPredictionData)
367 {
368 UVRCharacterMovementComponent* MutableThis = const_cast<UVRCharacterMovementComponent*>(this);
369 MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_VRCharacter(*this);
370 }
371
372 return ClientPredictionData;
373}
374
376{
377 // Should only be called on server in network games
378 check(CharacterOwner != NULL);
379 check(CharacterOwner->GetLocalRole() == ROLE_Authority);
380 checkSlow(GetNetMode() < NM_Client);
381
382 if (!ServerPredictionData)
383 {
384 UVRCharacterMovementComponent* MutableThis = const_cast<UVRCharacterMovementComponent*>(this);
385 MutableThis->ServerPredictionData = new FNetworkPredictionData_Server_VRCharacter(*this);
386 }
387
388 return ServerPredictionData;
389}
390
392{
393
394 // See if we can get the VR capsule location
395 if (AVRCharacter * VRC = Cast<AVRCharacter>(C))
396 {
397 UVRCharacterMovementComponent * CharMove = Cast<UVRCharacterMovementComponent>(VRC->GetCharacterMovement());
398 if (VRC->VRRootReference)
399 {
400 VRCapsuleLocation = VRC->VRRootReference->curCameraLoc;
401 VRCapsuleRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRC->VRRootReference->curCameraRot);
402 LFDiff = VRC->VRRootReference->DifferenceFromLastFrame;
403 }
404 else
405 {
406 VRCapsuleLocation = FVector::ZeroVector;
407 VRCapsuleRotation = FRotator::ZeroRotator;
408 LFDiff = FVector::ZeroVector;
409 }
410 }
411
413}
414
416{
417 UVRCharacterMovementComponent * CharMove = Cast<UVRCharacterMovementComponent>(Character->GetCharacterMovement());
418
419 // Set capsule location prior to testing movement
420 // I am overriding the replicated value here when movement is made on purpose
421 if (CharMove && CharMove->VRRootCapsule)
422 {
424 CharMove->VRRootCapsule->curCameraRot = this->VRCapsuleRotation;//FRotator(0.0f, FRotator::DecompressAxisFromByte(CapsuleYaw), 0.0f);
425 CharMove->VRRootCapsule->DifferenceFromLastFrame = FVector(LFDiff.X, LFDiff.Y, 0.0f);
427
428 if (AVRBaseCharacter * BaseChar = Cast<AVRBaseCharacter>(CharMove->GetCharacterOwner()))
429 {
430 if (BaseChar->VRReplicateCapsuleHeight && this->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(this->LFDiff.Z, CharMove->VRRootCapsule->GetUnscaledCapsuleHalfHeight()))
431 {
432 BaseChar->SetCharacterHalfHeightVR(LFDiff.Z, false);
433 //CharMove->VRRootCapsule->SetCapsuleHalfHeight(this->LFDiff.Z, false);
434 }
435 }
436
437 CharMove->VRRootCapsule->GenerateOffsetToWorld(false, false);
438 }
439
441}
442
443
445{
446 QUICK_SCOPE_CYCLE_COUNTER(VRCharacterMovementServerMove_PerformMovement);
447 //SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementServerMove);
448 //CSV_SCOPED_TIMING_STAT(CharacterMovement, CharacterMovementServerMove);
449
450 if (!HasValidData() || !IsActive())
451 {
452 return;
453 }
454
455 bool bAutoAcceptPacket = false;
456
457 FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character();
458 check(ServerData);
459
460 // Convert to our stored move data array
461 const FVRCharacterNetworkMoveData* MoveDataVR = (const FVRCharacterNetworkMoveData*)&MoveData;
462
463 if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated)
464 {
465 return;
466 }
468 {
469 // If the client isn't still registered as seated then we accept this first change of movement and go
470 ServerData->CurrentClientTimeStamp = MoveData.TimeStamp;
471 bAutoAcceptPacket = true;
472 bJustUnseated = false;
473 }
474
475 const float ClientTimeStamp = MoveData.TimeStamp;
476 FVector_NetQuantize10 ClientAccel = MoveData.Acceleration;
477 const uint8 ClientMoveFlags = MoveData.CompressedMoveFlags;
478 const FRotator ClientControlRotation = MoveData.ControlRotation;
479
480 if (!bAutoAcceptPacket && !VerifyClientTimeStamp(ClientTimeStamp, *ServerData))
481 {
482 const float ServerTimeStamp = ServerData->CurrentClientTimeStamp;
483 // This is more severe if the timestamp has a large discrepancy and hasn't been recently reset.
484 static const auto CVarNetServerMoveTimestampExpiredWarningThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.NetServerMoveTimestampExpiredWarningThreshold"));
485 if (ServerTimeStamp > 1.0f && FMath::Abs(ServerTimeStamp - ClientTimeStamp) > CVarNetServerMoveTimestampExpiredWarningThreshold->GetFloat())
486 {
487 UE_LOG(LogNetPlayerMovement, Warning, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner));
488 }
489 else
490 {
491 UE_LOG(LogNetPlayerMovement, Log, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner));
492 }
493 return;
494 }
495
496 // Scope these, they nest with Outer references so it should work fine, this keeps the update rotation and move autonomous from double updating the char
497 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
498
499 bool bServerReadyForClient = true;
500 APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController());
501 if (PC)
502 {
503 bServerReadyForClient = PC->NotifyServerReceivedClientData(CharacterOwner, ClientTimeStamp);
504 if (!bServerReadyForClient)
505 {
506 ClientAccel = FVector::ZeroVector;
507 }
508 }
509
510 const UWorld* MyWorld = GetWorld();
511 /*const*/ float DeltaTime = 0.0f;
512
513 // If we are auto accepting this packet, then accept it and use the maximum delta time as we don't know how long it has actually been since
514 // The last valid update
515 if (bAutoAcceptPacket)
516 {
517 DeltaTime = ServerData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld);
518 }
519 else
520 {
521 DeltaTime = ServerData->GetServerMoveDeltaTime(ClientTimeStamp, CharacterOwner->GetActorTimeDilation(*MyWorld));
522 }
523
524 if (DeltaTime > 0.f)
525 {
526 ServerData->CurrentClientTimeStamp = ClientTimeStamp;
527 ServerData->ServerAccumulatedClientTimeStamp += DeltaTime;
528 ServerData->ServerTimeStamp = MyWorld->GetTimeSeconds();
529 ServerData->ServerTimeStampLastServerMove = ServerData->ServerTimeStamp;
530
532 {
533 PC->SetControlRotation(ClientControlRotation);
534 }
535
536 if (!bServerReadyForClient)
537 {
538 return;
539 }
540
541 // Perform actual movement
542 if ((MyWorld->GetWorldSettings()->GetPauserPlayerState() == NULL))
543 {
544 if (PC)
545 {
546 PC->UpdateRotation(DeltaTime);
547 }
548
549 if (!MoveDataVR->ConditionalMoveReps.RequestedVelocity.IsZero())
550 {
551 RequestedVelocity = MoveDataVR->ConditionalMoveReps.RequestedVelocity;
552 bHasRequestedVelocity = true;
553 }
554
558
559 // Set capsule location prior to testing movement
560 // I am overriding the replicated value here when movement is made on purpose
561 if (VRRootCapsule)
562 {
564 VRRootCapsule->curCameraRot = FRotator(0.0f, FRotator::DecompressAxisFromShort(MoveDataVR->VRCapsuleRotation), 0.0f);
565 VRRootCapsule->DifferenceFromLastFrame = FVector(MoveDataVR->LFDiff.X, MoveDataVR->LFDiff.Y, 0.0f);
567
569 {
570 if (BaseVRCharacterOwner->VRReplicateCapsuleHeight && MoveDataVR->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(MoveDataVR->LFDiff.Z, VRRootCapsule->GetUnscaledCapsuleHalfHeight()))
571 {
573 // BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = LFDiff.Z;
574 //VRRootCapsule->SetCapsuleHalfHeight(LFDiff.Z, false);
575 }
576 }
577
579
580 // #TODO: Should I actually implement the mesh translation from "Crouch"? Generally people are going to be
581 // IKing any mesh from the HMD instead.
582 /*
583 // Don't smooth this change in mesh position
584 if (bClientSimulation && CharacterOwner->Role == ROLE_SimulatedProxy)
585 {
586 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
587 if (ClientData && ClientData->MeshTranslationOffset.Z != 0.f)
588 {
589 ClientData->MeshTranslationOffset += FVector(0.f, 0.f, MeshAdjust);
590 ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset;
591 }
592 }
593 */
594 }
595
596 MoveAutonomous(ClientTimeStamp, DeltaTime, ClientMoveFlags, ClientAccel);
597 bHasRequestedVelocity = false;
598 }
599
600 UE_CLOG(CharacterOwner && UpdatedComponent, LogNetPlayerMovement, VeryVerbose, TEXT("ServerMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d)"),
601 ClientTimeStamp, *ClientAccel.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), DeltaTime, *GetMovementName(),
602 *GetNameSafe(GetMovementBase()), *CharacterOwner->GetBasedMovement().BoneName.ToString(), MovementBaseUtility::IsDynamicBase(GetMovementBase()) ? 1 : 0);
603 }
604
605 // #TODO: Handle this better at some point? Client also denies it later on during correction (ApplyNetworkMovementMode in base movement)
606 // Pre handling the errors, lets avoid rolling back to/from custom movement modes, they tend to be scripted and this can screw things up
607 const uint8 CurrentPackedMovementMode = PackNetworkMovementMode();
608 if (CurrentPackedMovementMode != MoveData.MovementMode)
609 {
610 TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None);
611 TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None);
612 uint8 NetCustomMode(0);
613 UnpackNetworkMovementMode(MoveData.MovementMode, NetMovementMode, NetCustomMode, NetGroundMode);
614
615 // Custom movement modes aren't going to be rolled back as they are client authed for our pawns
616 if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom)
617 {
618 if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)
619 SetMovementMode(NetMovementMode, NetCustomMode);
620 }
621 }
622
623 // Validate move only after old and first dual portion, after all moves are completed.
624 if (MoveData.NetworkMoveType == FCharacterNetworkMoveData::ENetworkMoveType::NewMove)
625 {
626 ServerMoveHandleClientErrorVR(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, ClientControlRotation.Yaw, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode);
627 //ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode);
628 }
629}
630
631/*void UVRCharacterMovementComponent::CallServerMove
632(
633 const class FSavedMove_Character* NewCMove,
634 const class FSavedMove_Character* OldCMove
635 )
636{
637 // This is technically "safe", I know for sure that I am using my own FSavedMove
638 // I would have like to not override any of this, but I need a lot more specific information about the pawn
639 // So just setting flags in the FSaved Move doesn't cut it
640 // I could see a problem if someone overrides this override though
641 const FSavedMove_VRCharacter * NewMove = (const FSavedMove_VRCharacter *)NewCMove;
642 const FSavedMove_VRCharacter * OldMove = (const FSavedMove_VRCharacter *)OldCMove;
643
644 check(NewMove != nullptr);
645 //uint32 ClientYawPitchINT = 0;
646 //uint8 ClientRollBYTE = 0;
647 //NewMove->GetPackedAngles(ClientYawPitchINT, ClientRollBYTE);
648 const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(NewMove->VRCapsuleRotation.Yaw);
649 const uint16 ClientYawShort = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw);
650
651 // Determine if we send absolute or relative location
652 UPrimitiveComponent* ClientMovementBase = NewMove->EndBase.Get();
653 const FName ClientBaseBone = NewMove->EndBoneName;
654 const FVector SendLocation = MovementBaseUtility::UseRelativeLocation(ClientMovementBase) ? NewMove->SavedRelativeLocation : NewMove->SavedLocation;
655
656 // send old move if it exists
657 if (OldMove)
658 {
659 //const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(OldMove->VRCapsuleRotation.Yaw);
660 ServerMoveVROld(OldMove->TimeStamp, OldMove->Acceleration, OldMove->GetCompressedFlags(), OldMove->ConditionalValues);
661 }
662
663 // Pass these in here, don't pass in to old move, it will receive the new move values in dual operations
664 // Will automatically not replicate them if movement base is nullptr (1 bit cost to check this)
665 FVRConditionalMoveRep2 NewMoveConds;
666 NewMoveConds.ClientMovementBase = ClientMovementBase;
667 NewMoveConds.ClientBaseBoneName = ClientBaseBone;
668
669 if (CharacterOwner && (CharacterOwner->bUseControllerRotationRoll || CharacterOwner->bUseControllerRotationPitch))
670 {
671 NewMoveConds.ClientPitch = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Pitch);
672 NewMoveConds.ClientRoll = FRotator::CompressAxisToByte(NewMove->SavedControlRotation.Roll);
673 }
674
675 NewMoveConds.ClientYaw = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw);
676
677 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
678 if (const FSavedMove_Character* const PendingMove = ClientData->PendingMove.Get())
679 {
680 // This should send same as the uint16 because it uses a packedINT send by default for shorts
681 //uint32 OldClientYawPitchINT = 0;
682 //uint8 OldClientRollBYTE = 0;
683 //ClientData->PendingMove->GetPackedAngles(OldClientYawPitchINT, OldClientRollBYTE);
684
685 uint32 cPitch = 0;
686 if (CharacterOwner && (CharacterOwner->bUseControllerRotationPitch))
687 cPitch = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Pitch);
688
689 uint32 cYaw = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw);
690
691 // Switch the order of pitch and yaw to place Yaw in smallest value, this will cut down on rep cost since normally pitch is zero'd out in VR
692 uint32 OldClientYawPitchINT = (cPitch << 16) | (cYaw);
693
694 FSavedMove_VRCharacter* oldMove = (FSavedMove_VRCharacter*)ClientData->PendingMove.Get();
695 const uint16 OldCapsuleYawShort = FRotator::CompressAxisToShort(oldMove->VRCapsuleRotation.Yaw);
696 //const uint16 OldClientYawShort = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw);
697
698
699 // If we delayed a move without root motion, and our new move has root motion, send these through a special function, so the server knows how to process them.
700 if ((PendingMove->RootMotionMontage == NULL) && (NewMove->RootMotionMontage != NULL))
701 {
702 // send two moves simultaneously
703 ServerMoveVRDualHybridRootMotion
704 (
705 PendingMove->TimeStamp,
706 PendingMove->Acceleration,
707 PendingMove->GetCompressedFlags(),
708 OldClientYawPitchINT,
709 oldMove->VRCapsuleLocation,
710 oldMove->ConditionalValues,
711 oldMove->LFDiff,
712 OldCapsuleYawShort,
713 NewMove->TimeStamp,
714 NewMove->Acceleration,
715 SendLocation,
716 NewMove->VRCapsuleLocation,
717 NewMove->ConditionalValues,
718 NewMove->LFDiff,
719 CapsuleYawShort,
720 NewMove->GetCompressedFlags(),
721 NewMoveConds,
722 NewMove->EndPackedMovementMode
723 );
724 }
725 else // Not Hybrid root motion rpc
726 {
727 // send two moves simultaneously
728 if (oldMove->Acceleration.IsZero() && NewMove->Acceleration.IsZero())
729 {
730 ServerMoveVRDualExLight
731 (
732 PendingMove->TimeStamp,
733 PendingMove->GetCompressedFlags(),
734 OldClientYawPitchINT,
735 oldMove->VRCapsuleLocation,
736 oldMove->ConditionalValues,
737 oldMove->LFDiff,
738 OldCapsuleYawShort,
739 NewMove->TimeStamp,
740 SendLocation,
741 NewMove->VRCapsuleLocation,
742 NewMove->ConditionalValues,
743 NewMove->LFDiff,
744 CapsuleYawShort,
745 NewMove->GetCompressedFlags(),
746 NewMoveConds,
747 NewMove->EndPackedMovementMode
748 );
749 }
750 else
751 {
752 ServerMoveVRDual
753 (
754 PendingMove->TimeStamp,
755 PendingMove->Acceleration,
756 PendingMove->GetCompressedFlags(),
757 OldClientYawPitchINT,
758 oldMove->VRCapsuleLocation,
759 oldMove->ConditionalValues,
760 oldMove->LFDiff,
761 OldCapsuleYawShort,
762 NewMove->TimeStamp,
763 NewMove->Acceleration,
764 SendLocation,
765 NewMove->VRCapsuleLocation,
766 NewMove->ConditionalValues,
767 NewMove->LFDiff,
768 CapsuleYawShort,
769 NewMove->GetCompressedFlags(),
770 NewMoveConds,
771 NewMove->EndPackedMovementMode
772 );
773 }
774 }
775 }
776 else
777 {
778
779 if (NewMove->Acceleration.IsZero())
780 {
781 ServerMoveVRExLight
782 (
783 NewMove->TimeStamp,
784 SendLocation,
785 NewMove->VRCapsuleLocation,
786 NewMove->ConditionalValues,
787 NewMove->LFDiff,
788 CapsuleYawShort,
789 NewMove->GetCompressedFlags(),
790 NewMoveConds,
791 NewMove->EndPackedMovementMode
792 );
793 }
794 else
795 {
796 ServerMoveVR
797 (
798 NewMove->TimeStamp,
799 NewMove->Acceleration,
800 SendLocation,
801 NewMove->VRCapsuleLocation,
802 NewMove->ConditionalValues,
803 NewMove->LFDiff,
804 CapsuleYawShort,
805 NewMove->GetCompressedFlags(),
806 NewMoveConds,
807 NewMove->EndPackedMovementMode
808 );
809 }
810 }
811
812 MarkForClientCameraUpdate();
813}*/
814
815bool UVRCharacterMovementComponent::ShouldCheckForValidLandingSpot(float DeltaTime, const FVector& Delta, const FHitResult& Hit) const
816{
817 // See if we hit an edge of a surface on the lower portion of the capsule.
818 // In this case the normal will not equal the impact normal, and a downward sweep may find a walkable surface on top of the edge.
819 if (Hit.Normal.Z > KINDA_SMALL_NUMBER && !Hit.Normal.Equals(Hit.ImpactNormal))
820 {
821 FVector PawnLocation = UpdatedComponent->GetComponentLocation();
822 if (VRRootCapsule)
823 PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
824
825 if (IsWithinEdgeTolerance(PawnLocation, Hit.ImpactPoint, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius()))
826 {
827 return true;
828 }
829 }
830
831 return false;
832}
833
834void UVRCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations)
835{
836 SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking);
837
838 if (deltaTime < MIN_TICK_TIME)
839 {
840 return;
841 }
842
843 if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy)))
844 {
845 Acceleration = FVector::ZeroVector;
846 Velocity = FVector::ZeroVector;
847 return;
848 }
849
850 if (!UpdatedComponent->IsQueryCollisionEnabled())
851 {
852 SetMovementMode(MOVE_Walking);
853 return;
854 }
855
856 devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
857
858 bJustTeleported = false;
859 bool bCheckedFall = false;
860 bool bTriedLedgeMove = false;
861 float remainingTime = deltaTime;
862
863 // Rewind the players position by the new capsule location
865
866 // Perform the move
867 while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)))
868 {
869 Iterations++;
870 bJustTeleported = false;
871 const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
872 remainingTime -= timeTick;
873
874 // Save current values
875 UPrimitiveComponent * const OldBase = GetMovementBase();
876 const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector;
877 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
878
879 // Used for ledge check
880 FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation;
881
882 const FFindFloorResult OldFloor = CurrentFloor;
883
884 RestorePreAdditiveRootMotionVelocity();
885 //RestorePreAdditiveVRMotionVelocity();
886
887 // Ensure velocity is horizontal.
888 MaintainHorizontalGroundVelocity();
889 const FVector OldVelocity = Velocity;
890 Acceleration.Z = 0.f;
891
892 // Apply acceleration
893 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
894 {
895 CalcVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration());
896 devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
897 }
898
899 ApplyRootMotionToVelocity(timeTick);
900 ApplyVRMotionToVelocity(deltaTime);//timeTick);
901
902 devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
903
904 if (IsFalling())
905 {
906 // Root motion could have put us into Falling.
907 // No movement has taken place this movement tick so we pass on full time/past iteration count
908 StartNewPhysics(remainingTime + timeTick, Iterations - 1);
909 return;
910 }
911
912 // Compute move parameters
913 const FVector MoveVelocity = Velocity;
914
915 const FVector Delta = timeTick * MoveVelocity;
916
917 const bool bZeroDelta = Delta.IsNearlyZero();
918 FStepDownResult StepDownResult;
919
920 if (bZeroDelta)
921 {
922 remainingTime = 0.f;
923 // TODO: Bugged currently
924 /*if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride)
925 {
926
927 FHitResult HitRes;
928 FCollisionQueryParams Params("RelativeMovementSweep", false, GetOwner());
929 FCollisionResponseParams ResponseParam;
930
931 VRRootCapsule->InitSweepCollisionParams(Params, ResponseParam);
932 Params.bFindInitialOverlaps = true;
933 bool bWasBlockingHit = false;
934
935 bWasBlockingHit = GetWorld()->SweepSingleByChannel(HitRes, VRRootCapsule->OffsetComponentToWorld.GetLocation(), VRRootCapsule->OffsetComponentToWorld.GetLocation() + VRRootCapsule->DifferenceFromLastFrame, FQuat(0.0f, 0.0f, 0.0f, 1.0f), VRRootCapsule->GetCollisionObjectType(), VRRootCapsule->GetCollisionShape(), Params, ResponseParam);
936
937 const FVector GravDir(0.f, 0.f, -1.f);
938 if (CanStepUp(HitRes) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == HitRes.GetActor()))
939 StepUp(GravDir,VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), HitRes, &StepDownResult);
940 }*/
941 }
942 else
943 {
944 // try to move forward
945 MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult);
946
947 if (IsFalling())
948 {
949 // pawn decided to jump up
950 const float DesiredDist = Delta.Size();
951 if (DesiredDist > KINDA_SMALL_NUMBER)
952 {
953 const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D();
954 remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist));
955 }
957 StartNewPhysics(remainingTime, Iterations);
958 return;
959 }
960 else if (IsSwimming()) //just entered water
961 {
963 StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations);
964 return;
965 }
966 }
967
968 // Update floor.
969 // StepUp might have already done it for us.
970 if (StepDownResult.bComputedFloor)
971 {
972 CurrentFloor = StepDownResult.FloorResult;
973 }
974 else
975 {
976 FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL);
977 }
978
979 // check for ledges here
980 const bool bCheckLedges = !CanWalkOffLedges();
981 if (bCheckLedges && !CurrentFloor.IsWalkableFloor())
982 {
983 // calculate possible alternate movement
984 const FVector GravDir = FVector(0.f, 0.f, -1.f);
985 const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldCapsuleLocation, Delta, GravDir);
986 if (!NewDelta.IsZero())
987 {
988 // first revert this move
989 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false);
990
991 // avoid repeated ledge moves if the first one fails
992 bTriedLedgeMove = true;
993
994 // Try new movement direction
995 Velocity = NewDelta / timeTick;
996 remainingTime += timeTick;
998 continue;
999 }
1000 else
1001 {
1002 // see if it is OK to jump
1003 // @todo collision : only thing that can be problem is that oldbase has world collision on
1004 /*bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
1005 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
1006 {
1007 RestorePreAdditiveVRMotionVelocity();
1008 return;
1009 }*/
1010 bCheckedFall = true;
1011
1012 // revert this move
1013 RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true);
1014 remainingTime = 0.f;
1016 break;
1017 }
1018 }
1019 else
1020 {
1021 // Validate the floor check
1022 if (CurrentFloor.IsWalkableFloor())
1023 {
1024 if (ShouldCatchAir(OldFloor, CurrentFloor))
1025 {
1027 HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick);
1028 if (IsMovingOnGround())
1029 {
1030 // If still walking, then fall. If not, assume the user set a different mode they want to keep.
1031 StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation);
1032 }
1033 return;
1034 }
1035
1036 AdjustFloorHeight();
1037 SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
1038 }
1039 else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f)
1040 {
1041 // The floor check failed because it started in penetration
1042 // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
1043 FHitResult Hit(CurrentFloor.HitResult);
1044 Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST);
1045 const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
1046 ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat());
1047 bForceNextFloorCheck = true;
1048 }
1049
1050 // check if just entered water
1051 if (IsSwimming())
1052 {
1054 StartSwimmingVR(OldCapsuleLocation, Velocity, timeTick, remainingTime, Iterations);
1055 return;
1056 }
1057
1058 // See if we need to start falling.
1059 if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating)
1060 {
1061 const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase)));
1062 if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump))
1063 {
1065 return;
1066 }
1067 bCheckedFall = true;
1068 }
1069 }
1070
1071
1072 // Allow overlap events and such to change physics state and velocity
1073 if (IsMovingOnGround())
1074 {
1075 // Make velocity reflect actual move
1076
1077 if (!bJustTeleported && timeTick >= MIN_TICK_TIME)
1078 {
1079 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
1080 {
1081 // TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes?
1082 Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick);
1083 MaintainHorizontalGroundVelocity();
1084 }
1085
1087 }
1088 }
1089
1090 // If we didn't move at all this iteration then abort (since future iterations will also be stuck).
1091 if (UpdatedComponent->GetComponentLocation() == OldLocation)
1092 {
1094 remainingTime = 0.f;
1095 break;
1096 }
1097 }
1098
1099 if (IsMovingOnGround())
1100 {
1101 MaintainHorizontalGroundVelocity();
1102 }
1103}
1104
1105void UVRCharacterMovementComponent::CapsuleTouched(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
1106{
1107 if (!bEnablePhysicsInteraction)
1108 {
1109 return;
1110 }
1111
1112 if (OtherComp != NULL && OtherComp->IsAnySimulatingPhysics())
1113 {
1114 /*const*/FVector OtherLoc = OtherComp->GetComponentLocation();
1115 if (UVRRootComponent * rCap = Cast<UVRRootComponent>(OtherComp))
1116 {
1117 OtherLoc = rCap->OffsetComponentToWorld.GetLocation();
1118 }
1119
1120 const FVector Loc = VRRootCapsule->OffsetComponentToWorld.GetLocation();//UpdatedComponent->GetComponentLocation();
1121 FVector ImpulseDir = FVector(OtherLoc.X - Loc.X, OtherLoc.Y - Loc.Y, 0.25f).GetSafeNormal();
1122 ImpulseDir = (ImpulseDir + Velocity.GetSafeNormal2D()) * 0.5f;
1123 ImpulseDir.Normalize();
1124
1125 FName BoneName = NAME_None;
1126 if (OtherBodyIndex != INDEX_NONE)
1127 {
1128 BoneName = ((USkinnedMeshComponent*)OtherComp)->GetBoneName(OtherBodyIndex);
1129 }
1130
1131 float TouchForceFactorModified = TouchForceFactor;
1132
1133 if (bTouchForceScaledToMass)
1134 {
1135 FBodyInstance* BI = OtherComp->GetBodyInstance(BoneName);
1136 TouchForceFactorModified *= BI ? BI->GetBodyMass() : 1.0f;
1137 }
1138
1139 float ImpulseStrength = FMath::Clamp(Velocity.Size2D() * TouchForceFactorModified,
1140 MinTouchForce > 0.0f ? MinTouchForce : -FLT_MAX,
1141 MaxTouchForce > 0.0f ? MaxTouchForce : FLT_MAX);
1142
1143 FVector Impulse = ImpulseDir * ImpulseStrength;
1144
1145 OtherComp->AddImpulse(Impulse, BoneName);
1146 }
1147}
1148
1149
1150void UVRCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration)
1151{
1152 SCOPE_CYCLE_COUNTER(STAT_CharacterMovementReplicateMoveToServer);
1153 check(CharacterOwner != NULL);
1154
1155 // Can only start sending moves if our controllers are synced up over the network, otherwise we flood the reliable buffer.
1156 APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController());
1157 if (PC && PC->AcknowledgedPawn != CharacterOwner)
1158 {
1159 return;
1160 }
1161
1162 // Bail out if our character's controller doesn't have a Player. This may be the case when the local player
1163 // has switched to another controller, such as a debug camera controller.
1164 if (PC && PC->Player == nullptr)
1165 {
1166 return;
1167 }
1168
1169 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
1170 if (!ClientData)
1171 {
1172 return;
1173 }
1174
1175 // Update our delta time for physics simulation.
1176 DeltaTime = ClientData->UpdateTimeStampAndDeltaTime(DeltaTime, *CharacterOwner, *this);
1177
1178 // Find the oldest (unacknowledged) important move (OldMove).
1179 // Don't include the last move because it may be combined with the next new move.
1180 // A saved move is interesting if it differs significantly from the last acknowledged move
1181 FSavedMovePtr OldMove = NULL;
1182 if (ClientData->LastAckedMove.IsValid())
1183 {
1184 for (int32 i = 0; i < ClientData->SavedMoves.Num() - 1; i++)
1185 {
1186 const FSavedMovePtr& CurrentMove = ClientData->SavedMoves[i];
1187 if (CurrentMove->IsImportantMove(ClientData->LastAckedMove))
1188 {
1189 OldMove = CurrentMove;
1190 break;
1191 }
1192 }
1193 }
1194
1195 // Get a SavedMove object to store the movement in.
1196 FSavedMovePtr NewMovePtr = ClientData->CreateSavedMove();
1197 FSavedMove_Character* const NewMove = NewMovePtr.Get();
1198 if (NewMove == nullptr)
1199 {
1200 return;
1201 }
1202
1203 NewMove->SetMoveFor(CharacterOwner, DeltaTime, NewAcceleration, *ClientData);
1204 const UWorld* MyWorld = GetWorld();
1205 // Causing really bad crash when using vr offset location, rather remove for now than have it merge move improperly.
1206
1207 // see if the two moves could be combined
1208 // do not combine moves which have different TimeStamps (before and after reset).
1209 if (const FSavedMove_Character* PendingMove = ClientData->PendingMove.Get())
1210 {
1211 if (bAllowMovementMerging && PendingMove->CanCombineWith(NewMovePtr, CharacterOwner, ClientData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld)))
1212 {
1213 QUICK_SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementComponent_CombineNetMove);
1214 //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCombineNetMove);
1215
1216 // Only combine and move back to the start location if we don't move back in to a spot that would make us collide with something new.
1217 /*const */FVector OldStartLocation = PendingMove->GetRevertedLocation();
1218
1219 // Modifying the location to account for capsule loc
1220 FVector OverlapLocation = OldStartLocation;
1221 if (VRRootCapsule)
1222 OverlapLocation += VRRootCapsule->OffsetComponentToWorld.GetLocation() - VRRootCapsule->GetComponentLocation();
1223
1224 const bool bAttachedToObject = (NewMovePtr->StartAttachParent != nullptr);
1225 if (bAttachedToObject || !OverlapTest(OverlapLocation, PendingMove->StartRotation.Quaternion(), UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CharacterOwner))
1226 {
1227 // Avoid updating Mesh bones to physics during the teleport back, since PerformMovement() will update it right away anyway below.
1228 // Note: this must be before the FScopedMovementUpdate below, since that scope is what actually moves the character and mesh.
1229 //AVRBaseCharacter * BaseCharacter = Cast<AVRBaseCharacter>(CharacterOwner);
1230
1231 FScopedMeshBoneUpdateOverrideVR ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones);
1232
1233 // Accumulate multiple transform updates until scope ends.
1234 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
1235 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("CombineMove: add delta %f + %f and revert from %f %f to %f %f"), DeltaTime, ClientData->PendingMove->DeltaTime, UpdatedComponent->GetComponentLocation().X, UpdatedComponent->GetComponentLocation().Y, /*OldStartLocation.X*/OverlapLocation.X, /*OldStartLocation.Y*/OverlapLocation.Y);
1236
1237 NewMove->CombineWith(PendingMove, CharacterOwner, PC, OldStartLocation);
1238
1239 /************************/
1240 if (PC)
1241 {
1242 // We reverted position to that at the start of the pending move (above), however some code paths expect rotation to be set correctly
1243 // before character movement occurs (via FaceRotation), so try that now. The bOrientRotationToMovement path happens later as part of PerformMovement() and PhysicsRotation().
1244 CharacterOwner->FaceRotation(PC->GetControlRotation(), NewMove->DeltaTime);
1245 }
1246
1247 SaveBaseLocation();
1248 NewMove->SetInitialPosition(CharacterOwner);
1249
1250 // Remove pending move from move list. It would have to be the last move on the list.
1251 if (ClientData->SavedMoves.Num() > 0 && ClientData->SavedMoves.Last() == ClientData->PendingMove)
1252 {
1253 const bool bAllowShrinking = false;
1254 ClientData->SavedMoves.Pop(bAllowShrinking);
1255 }
1256 ClientData->FreeMove(ClientData->PendingMove);
1257 ClientData->PendingMove = nullptr;
1258 PendingMove = nullptr; // Avoid dangling reference, it's deleted above.
1259 }
1260 else
1261 {
1262 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [would collide at start location]"));
1263 }
1264 }
1265 /*else
1266 {
1267 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [not allowed by CanCombineWith()]"));
1268 }*/
1269 }
1270
1271 // Acceleration should match what we send to the server, plus any other restrictions the server also enforces (see MoveAutonomous).
1272 Acceleration = NewMove->Acceleration.GetClampedToMaxSize(GetMaxAcceleration());
1273 AnalogInputModifier = ComputeAnalogInputModifier(); // recompute since acceleration may have changed.
1274
1275 // Perform the move locally
1276 CharacterOwner->ClientRootMotionParams.Clear();
1277 CharacterOwner->SavedRootMotion.Clear();
1278 PerformMovement(NewMove->DeltaTime);
1279
1280 NewMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record);
1281
1282 // Add NewMove to the list
1283 if (CharacterOwner->IsReplicatingMovement())
1284 {
1285 check(NewMove == NewMovePtr.Get());
1286 ClientData->SavedMoves.Push(NewMovePtr);
1287
1288 static const auto CVarNetEnableMoveCombining = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableMoveCombining"));
1289 const bool bCanDelayMove = (CVarNetEnableMoveCombining->GetInt() != 0) && CanDelaySendingMove(NewMovePtr);
1290
1291 if (bCanDelayMove && ClientData->PendingMove.IsValid() == false)
1292 {
1293 // Decide whether to hold off on move
1294 // Decide whether to hold off on move
1295 const float NetMoveDelta = FMath::Clamp(GetClientNetSendDeltaTime(PC, ClientData, NewMovePtr), 1.f / 120.f, 1.f / 5.f);
1296
1297 if ((MyWorld->TimeSeconds - ClientData->ClientUpdateTime) * MyWorld->GetWorldSettings()->GetEffectiveTimeDilation() < NetMoveDelta)
1298 {
1299 // Delay sending this move.
1300 ClientData->PendingMove = NewMovePtr;
1301 return;
1302 }
1303 }
1304
1305 ClientData->ClientUpdateTime = MyWorld->TimeSeconds;
1306
1307 UE_CLOG(CharacterOwner&& UpdatedComponent, LogVRCharacterMovement, VeryVerbose, TEXT("ClientMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d) DualMove? %d"),
1308 NewMove->TimeStamp, *NewMove->Acceleration.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), NewMove->DeltaTime, *GetMovementName(),
1309 *GetNameSafe(NewMove->EndBase.Get()), *NewMove->EndBoneName.ToString(), MovementBaseUtility::IsDynamicBase(NewMove->EndBase.Get()) ? 1 : 0, ClientData->PendingMove.IsValid() ? 1 : 0);
1310
1311 bool bSendServerMove = true;
1312
1313#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
1314
1315 // Testing options: Simulated packet loss to server
1316 const float TimeSinceLossStart = (MyWorld->RealTimeSeconds - ClientData->DebugForcedPacketLossTimerStart);
1317
1318 static const auto CVarNetForceClientServerMoveLossDuration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossDuration"));
1319 static const auto CVarNetForceClientServerMoveLossPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossPercent"));
1320 if (ClientData->DebugForcedPacketLossTimerStart > 0.f && (TimeSinceLossStart < CVarNetForceClientServerMoveLossDuration->GetFloat()))
1321 {
1322 bSendServerMove = false;
1323 UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat() - TimeSinceLossStart);
1324 }
1325 else if (CVarNetForceClientServerMoveLossPercent->GetFloat() != 0.f && (RandomStream.FRand() < CVarNetForceClientServerMoveLossPercent->GetFloat()))
1326 {
1327 bSendServerMove = false;
1328 ClientData->DebugForcedPacketLossTimerStart = (CVarNetForceClientServerMoveLossDuration->GetFloat() > 0) ? MyWorld->RealTimeSeconds : 0.0f;
1329 UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat());
1330 }
1331 else
1332 {
1333 ClientData->DebugForcedPacketLossTimerStart = 0.f;
1334 }
1335#endif
1336
1337 // Send move to server if this character is replicating movement
1338 if (bSendServerMove)
1339 {
1340 SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCallServerMove);
1341 if (ShouldUsePackedMovementRPCs())
1342 {
1343 CallServerMovePacked(NewMove, ClientData->PendingMove.Get(), OldMove.Get());
1344 }
1345 /*else
1346 {
1347 CallServerMove(NewMove, OldMove.Get());
1348 }*/
1349 }
1350 }
1351
1352 ClientData->PendingMove = NULL;
1353}
1354
1355/*
1356*
1357*
1358* END TEST AREA
1359*
1360*
1361*/
1362
1363UVRCharacterMovementComponent::UVRCharacterMovementComponent(const FObjectInitializer& ObjectInitializer)
1364 : Super(ObjectInitializer)
1365{
1366 PostPhysicsTickFunction.bCanEverTick = true;
1367 PostPhysicsTickFunction.bStartWithTickEnabled = false;
1368 PrimaryComponentTick.TickGroup = TG_PrePhysics;
1369 VRRootCapsule = NULL;
1370 //VRCameraCollider = NULL;
1371 // 0.1f is low slide and still impacts surfaces well
1372 // This variable is a bit of a hack, it reduces the movement of the pawn in the direction of relative movement
1373 //WallRepulsionMultiplier = 0.01f;
1375 bAllowMovementMerging = true;
1377 bRequestedMoveUseAcceleration = false;
1378}
1379
1381{
1382 Super::OnRegister();
1383
1384 // they enforce linear usually
1385 // Force exponential smoothing for replays as it works best for our vr stuff
1386 const UWorld* MyWorld = GetWorld();
1387 const bool bIsReplay = (MyWorld && MyWorld->IsPlayingReplay());
1388
1389 if (bIsReplay)
1390 {
1391 //NetworkSmoothingMode = ENetworkSmoothingMode::Linear;
1392 NetworkSmoothingMode = ENetworkSmoothingMode::Exponential;
1393 }
1394}
1395
1396
1397void UVRCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
1398{
1399 if (!HasValidData())
1400 {
1401 return;
1402 }
1403
1404 if (CharacterOwner && CharacterOwner->IsLocallyControlled())
1405 {
1406 // Root capsule is now throwing out the difference itself, I use the difference for multiplayer sends
1407 if (VRRootCapsule)
1408 {
1410 }
1411 else
1412 {
1413 AdditionalVRInputVector = FVector::ZeroVector;
1414 }
1415 }
1416
1417 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
1418}
1419
1420// No support for crouching code yet
1422{
1423 return false;
1424}
1425
1426
1428{
1429 if (UpdatedPrimitive && RepulsionForce > 0.0f && CharacterOwner != nullptr)
1430 {
1431 const TArray<FOverlapInfo>& Overlaps = UpdatedPrimitive->GetOverlapInfos();
1432 if (Overlaps.Num() > 0)
1433 {
1434 FCollisionQueryParams QueryParams;
1435 QueryParams.bReturnFaceIndex = false;
1436 QueryParams.bReturnPhysicalMaterial = false;
1437
1438 float CapsuleRadius = 0.f;
1439 float CapsuleHalfHeight = 0.f;
1440 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(CapsuleRadius, CapsuleHalfHeight);
1441 const float RepulsionForceRadius = CapsuleRadius * 1.2f;
1442 const float StopBodyDistance = 2.5f;
1443 FVector MyLocation;
1444
1445 if (VRRootCapsule)
1446 MyLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
1447 else
1448 MyLocation = UpdatedPrimitive->GetComponentLocation();
1449
1450 for (int32 i = 0; i < Overlaps.Num(); i++)
1451 {
1452 const FOverlapInfo& Overlap = Overlaps[i];
1453
1454 UPrimitiveComponent* OverlapComp = Overlap.OverlapInfo.Component.Get();
1455 if (!OverlapComp || OverlapComp->Mobility < EComponentMobility::Movable)
1456 {
1457 continue;
1458 }
1459
1460 // Use the body instead of the component for cases where we have multi-body overlaps enabled
1461 FBodyInstance* OverlapBody = nullptr;
1462 const int32 OverlapBodyIndex = Overlap.GetBodyIndex();
1463 const USkeletalMeshComponent* SkelMeshForBody = (OverlapBodyIndex != INDEX_NONE) ? Cast<USkeletalMeshComponent>(OverlapComp) : nullptr;
1464 if (SkelMeshForBody != nullptr)
1465 {
1466 OverlapBody = SkelMeshForBody->Bodies.IsValidIndex(OverlapBodyIndex) ? SkelMeshForBody->Bodies[OverlapBodyIndex] : nullptr;
1467 }
1468 else
1469 {
1470 OverlapBody = OverlapComp->GetBodyInstance();
1471 }
1472
1473 if (!OverlapBody)
1474 {
1475 UE_LOG(LogVRCharacterMovement, Warning, TEXT("%s could not find overlap body for body index %d"), *GetName(), OverlapBodyIndex);
1476 continue;
1477 }
1478
1479 if (!OverlapBody->IsInstanceSimulatingPhysics())
1480 {
1481 continue;
1482 }
1483
1484 FTransform BodyTransform = OverlapBody->GetUnrealWorldTransform();
1485
1486 FVector BodyVelocity = OverlapBody->GetUnrealWorldVelocity();
1487 FVector BodyLocation = BodyTransform.GetLocation();
1488
1489 // Trace to get the hit location on the capsule
1490 FHitResult Hit;
1491 bool bHasHit = UpdatedPrimitive->LineTraceComponent(Hit, BodyLocation,
1492 FVector(MyLocation.X, MyLocation.Y, BodyLocation.Z),
1493 QueryParams);
1494
1495 FVector HitLoc = Hit.ImpactPoint;
1496 bool bIsPenetrating = Hit.bStartPenetrating || Hit.PenetrationDepth > StopBodyDistance;
1497
1498 // If we didn't hit the capsule, we're inside the capsule
1499 if (!bHasHit)
1500 {
1501 HitLoc = BodyLocation;
1502 bIsPenetrating = true;
1503 }
1504
1505 const float DistanceNow = (HitLoc - BodyLocation).SizeSquared2D();
1506 const float DistanceLater = (HitLoc - (BodyLocation + BodyVelocity * DeltaSeconds)).SizeSquared2D();
1507
1508 if (bHasHit && DistanceNow < StopBodyDistance && !bIsPenetrating)
1509 {
1510 OverlapBody->SetLinearVelocity(FVector(0.0f, 0.0f, 0.0f), false);
1511 }
1512 else if (DistanceLater <= DistanceNow || bIsPenetrating)
1513 {
1514 FVector ForceCenter = MyLocation;
1515
1516 if (bHasHit)
1517 {
1518 ForceCenter.Z = HitLoc.Z;
1519 }
1520 else
1521 {
1522 ForceCenter.Z = FMath::Clamp(BodyLocation.Z, MyLocation.Z - CapsuleHalfHeight, MyLocation.Z + CapsuleHalfHeight);
1523 }
1524
1525 OverlapBody->AddRadialForceToBody(ForceCenter, RepulsionForceRadius, RepulsionForce * Mass, ERadialImpulseFalloff::RIF_Constant);
1526 }
1527 }
1528 }
1529 }
1530}
1531
1532
1534{
1535 Super::SetUpdatedComponent(NewUpdatedComponent);
1536
1537 if (UpdatedComponent)
1538 {
1539 // Fill the VRRootCapsule if we can
1540 VRRootCapsule = Cast<UVRRootComponent>(UpdatedComponent);
1541
1542 // Fill in the camera collider if we can
1543 /*if (AVRCharacter * vrOwner = Cast<AVRCharacter>(this->GetOwner()))
1544 {
1545 VRCameraCollider = vrOwner->VRCameraCollider;
1546 }*/
1547
1548 // Stop the tick forcing
1549 UpdatedComponent->PrimaryComponentTick.RemovePrerequisite(this, PrimaryComponentTick);
1550
1551 // Start forcing the root to tick before this, the actor tick will still tick after the movement component
1552 // We want the root component to tick first because it is setting its offset location based off of tick
1553 this->PrimaryComponentTick.AddPrerequisite(UpdatedComponent, UpdatedComponent->PrimaryComponentTick);
1554 }
1555}
1556
1557FORCEINLINE_DEBUGGABLE bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport)
1558{
1559 return SafeMoveUpdatedComponent(Delta, NewRotation.Quaternion(), bSweep, OutHit, Teleport);
1560}
1561
1562bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport)
1563{
1564 if (UpdatedComponent == NULL)
1565 {
1566 OutHit.Reset(1.f);
1567 return false;
1568 }
1569
1570 bool bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &OutHit, Teleport);
1571
1572 // Handle initial penetrations
1573 if (OutHit.bStartPenetrating && UpdatedComponent)
1574 {
1575 const FVector RequestedAdjustment = GetPenetrationAdjustment(OutHit);
1576 if (ResolvePenetration(RequestedAdjustment, OutHit, NewRotation))
1577 {
1578 FHitResult TempHit;
1579 // Retry original move
1580 bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &TempHit, Teleport);
1581
1582 // Remove start penetrating if this is a clean move, otherwise use the last moves hit as the result so step up actually attempts to work.
1583 if (TempHit.bStartPenetrating)
1584 OutHit = TempHit;
1585 else
1586 OutHit.bStartPenetrating = TempHit.bStartPenetrating;
1587 }
1588 }
1589
1590 return bMoveResult;
1591}
1592
1593void UVRCharacterMovementComponent::MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult)
1594{
1595 if (!CurrentFloor.IsWalkableFloor())
1596 {
1597 return;
1598 }
1599
1600 // Move along the current floor
1601 const FVector Delta = FVector(InVelocity.X, InVelocity.Y, 0.f) * DeltaSeconds;
1602 FHitResult Hit(1.f);
1603 FVector RampVector = ComputeGroundMovementDelta(Delta, CurrentFloor.HitResult, CurrentFloor.bLineTrace);
1604 SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);
1605 float LastMoveTimeSlice = DeltaSeconds;
1606
1607 if (Hit.bStartPenetrating)
1608 {
1609 // Allow this hit to be used as an impact we can deflect off, otherwise we do nothing the rest of the update and appear to hitch.
1610 HandleImpact(Hit);
1611 SlideAlongSurface(Delta, 1.f, Hit.Normal, Hit, true);
1612
1613 if (Hit.bStartPenetrating)
1614 {
1615 OnCharacterStuckInGeometry(&Hit);
1616 }
1617 }
1618 else if (Hit.IsValidBlockingHit())
1619 {
1620 // We impacted something (most likely another ramp, but possibly a barrier).
1621 float PercentTimeApplied = Hit.Time;
1622 if ((Hit.Time > 0.f) && (Hit.Normal.Z > KINDA_SMALL_NUMBER) && IsWalkable(Hit))
1623 {
1624 // Another walkable ramp.
1625 const float InitialPercentRemaining = 1.f - PercentTimeApplied;
1626 RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false);
1627 LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice;
1628 SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit);
1629
1630 const float SecondHitPercent = Hit.Time * InitialPercentRemaining;
1631 PercentTimeApplied = FMath::Clamp(PercentTimeApplied + SecondHitPercent, 0.f, 1.f);
1632 }
1633
1634 if (Hit.IsValidBlockingHit())
1635 {
1636 if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == Hit.GetActor()))
1637 {
1638 // hit a barrier, try to step up
1639 const FVector PreStepUpLocation = UpdatedComponent->GetComponentLocation();
1640 const FVector GravDir(0.f, 0.f, -1.f);
1641
1642 // I add in the HMD difference from last frame to the step up check to enforce it stepping up
1643 if (!StepUp(GravDir, (Delta * (1.f - PercentTimeApplied)) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, OutStepDownResult))
1644 {
1645 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
1646 HandleImpact(Hit, LastMoveTimeSlice, RampVector);
1647 SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
1648
1649 }
1650 else
1651 {
1652 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("+ StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString());
1653 if (!bMaintainHorizontalGroundVelocity)
1654 {
1655 // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. Only consider horizontal movement.
1656 bJustTeleported = true;
1657 const float StepUpTimeSlice = (1.f - PercentTimeApplied) * DeltaSeconds;
1658 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && StepUpTimeSlice >= KINDA_SMALL_NUMBER)
1659 {
1660 Velocity = (UpdatedComponent->GetComponentLocation() - PreStepUpLocation) / StepUpTimeSlice;
1661 Velocity.Z = 0;
1662 }
1663 }
1664 }
1665 }
1666 else if (Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner))
1667 {
1668 HandleImpact(Hit, LastMoveTimeSlice, RampVector);
1669 SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true);
1670
1671 }
1672 }
1673 }
1674}
1675
1676bool UVRCharacterMovementComponent::StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult)
1677{
1678 SCOPE_CYCLE_COUNTER(STAT_CharStepUp);
1679
1680 if (!CanStepUp(InHit) || MaxStepHeight <= 0.f)
1681 {
1682 return false;
1683 }
1684
1685 FVector OldLocation;
1686
1687 if (VRRootCapsule)
1688 OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
1689 else
1690 OldLocation = UpdatedComponent->GetComponentLocation();
1691
1692 float PawnRadius, PawnHalfHeight;
1693 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
1694
1695 // Don't bother stepping up if top of capsule is hitting something.
1696 const float InitialImpactZ = InHit.ImpactPoint.Z;
1697 if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius))
1698 {
1699 return false;
1700 }
1701
1702 if (GravDir.IsZero())
1703 {
1704 return false;
1705 }
1706
1707 // Gravity should be a normalized direction
1708 ensure(GravDir.IsNormalized());
1709
1710 float StepTravelUpHeight = MaxStepHeight;
1711 float StepTravelDownHeight = StepTravelUpHeight;
1712 const float StepSideZ = -1.f * FVector::DotProduct(InHit.ImpactNormal, GravDir);//const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir);
1713 float PawnInitialFloorBaseZ = OldLocation.Z -PawnHalfHeight;
1714 float PawnFloorPointZ = PawnInitialFloorBaseZ;
1715
1716 if (IsMovingOnGround() && CurrentFloor.IsWalkableFloor())
1717 {
1718 // Since we float a variable amount off the floor, we need to enforce max step height off the actual point of impact with the floor.
1719 const float FloorDist = FMath::Max(0.f, CurrentFloor.GetDistanceToFloor());
1720 PawnInitialFloorBaseZ -= FloorDist;
1721 StepTravelUpHeight = FMath::Max(StepTravelUpHeight - FloorDist, 0.f);
1722 StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST*2.f);
1723
1724 const bool bHitVerticalFace = !IsWithinEdgeTolerance(InHit.Location, InHit.ImpactPoint, PawnRadius);
1725 if (!CurrentFloor.bLineTrace && !bHitVerticalFace)
1726 {
1727 PawnFloorPointZ = CurrentFloor.HitResult.ImpactPoint.Z;
1728 }
1729 else
1730 {
1731 // Base floor point is the base of the capsule moved down by how far we are hovering over the surface we are hitting.
1732 PawnFloorPointZ -= CurrentFloor.FloorDist;
1733 }
1734 }
1735
1736 // Don't step up if the impact is below us, accounting for distance from floor.
1737 if (InitialImpactZ <= PawnInitialFloorBaseZ)
1738 {
1739 return false;
1740 }
1741
1742 // Scope our movement updates, and do not apply them until all intermediate moves are completed.
1743 /*FScopedMovementUpdate*/ FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates);
1744
1745 // step up - treat as vertical wall
1746 FHitResult SweepUpHit(1.f);
1747 const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
1748 MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit);
1749
1750 if (SweepUpHit.bStartPenetrating)
1751 {
1752 // Undo movement
1753 ScopedStepUpMovement.RevertMove();
1754 return false;
1755 }
1756
1757
1758 // step fwd
1759 FHitResult Hit(1.f);
1760 MoveUpdatedComponent(Delta, PawnRotation, true, &Hit);
1761
1762 // Check result of forward movement
1763 if (Hit.bBlockingHit)
1764 {
1765 if (Hit.bStartPenetrating)
1766 {
1767 // Undo movement
1768 ScopedStepUpMovement.RevertMove();
1769 return false;
1770 }
1771
1772 // If we hit something above us and also something ahead of us, we should notify about the upward hit as well.
1773 // The forward hit will be handled later (in the bSteppedOver case below).
1774 // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification.
1775 if (SweepUpHit.bBlockingHit && Hit.bBlockingHit)
1776 {
1777 HandleImpact(SweepUpHit);
1778 }
1779
1780 // pawn ran into a wall
1781 HandleImpact(Hit);
1782 if (IsFalling())
1783 {
1784 return true;
1785 }
1786
1787 // adjust and try again
1788 const float ForwardHitTime = Hit.Time;
1789 const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true);
1790
1791 if (IsFalling())
1792 {
1793 ScopedStepUpMovement.RevertMove();
1794 return false;
1795 }
1796
1797 // If both the forward hit and the deflection got us nowhere, there is no point in this step up.
1798 if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f)
1799 {
1800 ScopedStepUpMovement.RevertMove();
1801 return false;
1802 }
1803 }
1804
1805 // Step down
1806 MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit);
1807
1808 // If step down was initially penetrating abort the step up
1809 if (Hit.bStartPenetrating)
1810 {
1811 ScopedStepUpMovement.RevertMove();
1812 return false;
1813 }
1814
1815 FStepDownResult StepDownResult;
1816 if (Hit.IsValidBlockingHit())
1817 {
1818 // See if this step sequence would have allowed us to travel higher than our max step height allows.
1819 const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ;
1820 if (DeltaZ > MaxStepHeight)
1821 {
1822 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ);
1823 ScopedStepUpMovement.RevertMove();
1824 return false;
1825 }
1826
1827 // Reject unwalkable surface normals here.
1828 if (!IsWalkable(Hit))
1829 {
1830 // Reject if normal opposes movement direction
1831 const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f;
1832 if (bNormalTowardsMe)
1833 {
1834 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString());
1835 ScopedStepUpMovement.RevertMove();
1836 return false;
1837 }
1838
1839 // Also reject if we would end up being higher than our starting location by stepping down.
1840 // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge.
1841 if (Hit.Location.Z > OldLocation.Z)
1842 {
1843 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString());
1844 ScopedStepUpMovement.RevertMove();
1845 return false;
1846 }
1847 }
1848
1849 // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well.
1850 if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius))
1851 {
1852 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)"));
1853 ScopedStepUpMovement.RevertMove();
1854 return false;
1855 }
1856
1857 // Don't step up onto invalid surfaces if traveling higher.
1858 if (DeltaZ > 0.f && !CanStepUp(Hit))
1859 {
1860 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())"));
1861 ScopedStepUpMovement.RevertMove();
1862 return false;
1863 }
1864
1865 // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method.
1866 if (OutStepDownResult != NULL)
1867 {
1868 FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit);
1869
1870 // Reject unwalkable normals if we end up higher than our initial height.
1871 // It's fine to walk down onto an unwalkable surface, don't reject those moves.
1872 if (Hit.Location.Z > OldLocation.Z)
1873 {
1874 // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare).
1875 // In those cases we should instead abort the step up and try to slide along the stair.
1876 if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < MAX_STEP_SIDE_Z)
1877 {
1878 ScopedStepUpMovement.RevertMove();
1879 return false;
1880 }
1881 }
1882
1883 StepDownResult.bComputedFloor = true;
1884 }
1885 }
1886
1887 // Copy step down result.
1888 if (OutStepDownResult != NULL)
1889 {
1890 *OutStepDownResult = StepDownResult;
1891 }
1892
1893 // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.
1894 bJustTeleported |= !bMaintainHorizontalGroundVelocity;
1895
1896 return true;
1897}
1898
1899bool UVRCharacterMovementComponent::IsWithinEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const
1900{
1901 const float DistFromCenterSq = (TestImpactPoint - CapsuleLocation).SizeSquared2D();
1902 const float ReducedRadiusSq = FMath::Square(FMath::Max(VREdgeRejectDistance + KINDA_SMALL_NUMBER, CapsuleRadius - VREdgeRejectDistance));
1903 return DistFromCenterSq < ReducedRadiusSq;
1904}
1905
1906bool UVRCharacterMovementComponent::IsWithinClimbingEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const
1907{
1908 const float DistFromCenterSq = (TestImpactPoint - CapsuleLocation).SizeSquared2D();
1909 const float ReducedRadiusSq = FMath::Square(FMath::Max(VRClimbingEdgeRejectDistance + KINDA_SMALL_NUMBER, CapsuleRadius - VRClimbingEdgeRejectDistance));
1910 return DistFromCenterSq < ReducedRadiusSq;
1911}
1912
1913bool UVRCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult)
1914{
1915 SCOPE_CYCLE_COUNTER(STAT_CharStepUp);
1916
1917 if (!CanStepUp(InHit) || MaxStepHeight <= 0.f)
1918 {
1919 return false;
1920 }
1921
1922 FVector OldLocation;
1923
1924 if (VRRootCapsule)
1925 OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
1926 else
1927 OldLocation = UpdatedComponent->GetComponentLocation();
1928
1929 float PawnRadius, PawnHalfHeight;
1930 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight);
1931
1932 // Don't bother stepping up if top of capsule is hitting something.
1933 const float InitialImpactZ = InHit.ImpactPoint.Z;
1934 if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius))
1935 {
1936 return false;
1937 }
1938
1939 // Don't step up if the impact is below us
1940 if (InitialImpactZ <= OldLocation.Z - PawnHalfHeight)
1941 {
1942 return false;
1943 }
1944
1945 if (GravDir.IsZero())
1946 {
1947 return false;
1948 }
1949
1950 // Gravity should be a normalized direction
1951 ensure(GravDir.IsNormalized());
1952
1953 float StepTravelUpHeight = MaxStepHeight;
1954 float StepTravelDownHeight = StepTravelUpHeight;
1955 const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir);
1956 float PawnInitialFloorBaseZ = OldLocation.Z - PawnHalfHeight;
1957 float PawnFloorPointZ = PawnInitialFloorBaseZ;
1958
1959 // Scope our movement updates, and do not apply them until all intermediate moves are completed.
1960 FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates);
1961
1962 // step up - treat as vertical wall
1963 FHitResult SweepUpHit(1.f);
1964 const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
1965 MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit);
1966
1967 if (SweepUpHit.bStartPenetrating)
1968 {
1969 // Undo movement
1970 ScopedStepUpMovement.RevertMove();
1971 return false;
1972 }
1973
1974 // step fwd
1975 FHitResult Hit(1.f);
1976
1977
1978 // Adding in the directional difference of the last HMD movement to promote stepping up
1979 // Probably entirely wrong as Delta is divided by movement ticks but I want the effect to be stronger anyway
1980 // This won't effect control based movement unless stepping forward at the same time, but gives RW movement
1981 // the extra boost to get up over a lip
1982 // #TODO test this more, currently appears to be needed for walking, but is harmful for other modes
1983// if (VRRootCapsule)
1984// MoveUpdatedComponent(Delta + VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), PawnRotation, true, &Hit);
1985// else
1986 MoveUpdatedComponent(Delta, PawnRotation, true, &Hit);
1987
1988 //MoveUpdatedComponent(Delta, PawnRotation, true, &Hit);
1989
1990 // Check result of forward movement
1991 if (Hit.bBlockingHit)
1992 {
1993 if (Hit.bStartPenetrating)
1994 {
1995 // Undo movement
1996 ScopedStepUpMovement.RevertMove();
1997 return false;
1998 }
1999
2000 // If we hit something above us and also something ahead of us, we should notify about the upward hit as well.
2001 // The forward hit will be handled later (in the bSteppedOver case below).
2002 // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification.
2003 if (SweepUpHit.bBlockingHit && Hit.bBlockingHit)
2004 {
2005
2006 HandleImpact(SweepUpHit);
2007 }
2008
2009 // pawn ran into a wall
2010 HandleImpact(Hit);
2011 if (IsFalling())
2012 {
2013 return true;
2014 }
2015
2016 //Don't adjust for VR, it doesn't work correctly
2017 ScopedStepUpMovement.RevertMove();
2018 return false;
2019
2020 // adjust and try again
2021 //const float ForwardHitTime = Hit.Time;
2022 //const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true);
2023
2024 //if (IsFalling())
2025 //{
2026 // ScopedStepUpMovement.RevertMove();
2027 // return false;
2028 //}
2029
2030 // If both the forward hit and the deflection got us nowhere, there is no point in this step up.
2031 //if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f)
2032 //{
2033 // ScopedStepUpMovement.RevertMove();
2034 // return false;
2035 //}
2036 }
2037
2038 // Step down
2039 MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit);
2040
2041 // If step down was initially penetrating abort the step up
2042 if (Hit.bStartPenetrating)
2043 {
2044 ScopedStepUpMovement.RevertMove();
2045 return false;
2046 }
2047
2048 FStepDownResult StepDownResult;
2049 if (Hit.IsValidBlockingHit())
2050 {
2051 // See if this step sequence would have allowed us to travel higher than our max step height allows.
2052 const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ;
2053 if (DeltaZ > MaxStepHeight)
2054 {
2055 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ);
2056 ScopedStepUpMovement.RevertMove();
2057 return false;
2058 }
2059
2060 // Reject unwalkable surface normals here.
2061 if (!IsWalkable(Hit))
2062 {
2063 // Reject if normal opposes movement direction
2064 const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f;
2065 if (bNormalTowardsMe)
2066 {
2067 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString());
2068 ScopedStepUpMovement.RevertMove();
2069 return false;
2070 }
2071
2072 // Also reject if we would end up being higher than our starting location by stepping down.
2073 // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge.
2074 if (Hit.Location.Z > OldLocation.Z)
2075 {
2076 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString());
2077 ScopedStepUpMovement.RevertMove();
2078 return false;
2079 }
2080 }
2081
2082 // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well.
2083 if (!IsWithinClimbingEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius))
2084 {
2085 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)"));
2086 ScopedStepUpMovement.RevertMove();
2087 return false;
2088 }
2089
2090 // Don't step up onto invalid surfaces if traveling higher.
2091 if (DeltaZ > 0.f && !CanStepUp(Hit))
2092 {
2093 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())"));
2094 ScopedStepUpMovement.RevertMove();
2095 return false;
2096 }
2097
2098 // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method.
2099 if (OutStepDownResult != NULL)
2100 {
2101 FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit);
2102
2103 // Reject unwalkable normals if we end up higher than our initial height.
2104 // It's fine to walk down onto an unwalkable surface, don't reject those moves.
2105 if (Hit.Location.Z > OldLocation.Z)
2106 {
2107 // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare).
2108 // In those cases we should instead abort the step up and try to slide along the stair.
2109 if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < MAX_STEP_SIDE_Z)
2110 {
2111 ScopedStepUpMovement.RevertMove();
2112 return false;
2113 }
2114 }
2115
2116 StepDownResult.bComputedFloor = true;
2117 }
2118 }
2119
2120 // Copy step down result.
2121 if (OutStepDownResult != NULL)
2122 {
2123 *OutStepDownResult = StepDownResult;
2124 }
2125
2126 // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments.
2127 bJustTeleported |= !bMaintainHorizontalGroundVelocity;
2128 return true;
2129}
2130
2131
2133{
2134 if (!HasValidData())
2135 {
2136 return;
2137 }
2138
2139 const UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase();
2140 if (!MovementBaseUtility::UseRelativeLocation(MovementBase))
2141 {
2142 return;
2143 }
2144
2145 if (!IsValid(MovementBase) || !IsValid(MovementBase->GetOwner()))
2146 {
2147 SetBase(NULL);
2148 return;
2149 }
2150
2151 // Ignore collision with bases during these movements.
2152 TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, MoveComponentFlags | MOVECOMP_IgnoreBases);
2153
2154 FQuat DeltaQuat = FQuat::Identity;
2155 FVector DeltaPosition = FVector::ZeroVector;
2156
2157 FQuat NewBaseQuat;
2158 FVector NewBaseLocation;
2159 if (!MovementBaseUtility::GetMovementBaseTransform(MovementBase, CharacterOwner->GetBasedMovement().BoneName, NewBaseLocation, NewBaseQuat))
2160 {
2161 return;
2162 }
2163
2164 // Find change in rotation
2165 const bool bRotationChanged = !OldBaseQuat.Equals(NewBaseQuat, 1e-8f);
2166 if (bRotationChanged)
2167 {
2168 DeltaQuat = NewBaseQuat * OldBaseQuat.Inverse();
2169 }
2170
2171 // only if base moved
2172 if (bRotationChanged || (OldBaseLocation != NewBaseLocation))
2173 {
2174 // Calculate new transform matrix of base actor (ignoring scale).
2175 const FQuatRotationTranslationMatrix OldLocalToWorld(OldBaseQuat, OldBaseLocation);
2176 const FQuatRotationTranslationMatrix NewLocalToWorld(NewBaseQuat, NewBaseLocation);
2177
2178 if (CharacterOwner->IsMatineeControlled())
2179 {
2180 FRotationTranslationMatrix HardRelMatrix(CharacterOwner->GetBasedMovement().Rotation, CharacterOwner->GetBasedMovement().Location);
2181 const FMatrix NewWorldTM = HardRelMatrix * NewLocalToWorld;
2182 const FQuat NewWorldRot = bIgnoreBaseRotation ? UpdatedComponent->GetComponentQuat() : NewWorldTM.ToQuat();
2183 MoveUpdatedComponent(NewWorldTM.GetOrigin() - UpdatedComponent->GetComponentLocation(), NewWorldRot, true);
2184 }
2185 else
2186 {
2187 FQuat FinalQuat = UpdatedComponent->GetComponentQuat();
2188
2189 if (bRotationChanged && !bIgnoreBaseRotation)
2190 {
2191 // Apply change in rotation and pipe through FaceRotation to maintain axis restrictions
2192 const FQuat PawnOldQuat = UpdatedComponent->GetComponentQuat();
2193 const FQuat TargetQuat = DeltaQuat * FinalQuat;
2194 FRotator TargetRotator(TargetQuat);
2195 CharacterOwner->FaceRotation(TargetRotator, 0.f);
2196 FinalQuat = UpdatedComponent->GetComponentQuat();
2197
2198 if (PawnOldQuat.Equals(FinalQuat, 1e-6f))
2199 {
2200 // Nothing changed. This means we probably are using another rotation mechanism (bOrientToMovement etc). We should still follow the base object.
2201 // @todo: This assumes only Yaw is used, currently a valid assumption. This is the only reason FaceRotation() is used above really, aside from being a virtual hook.
2202 if (bOrientRotationToMovement || (bUseControllerDesiredRotation && CharacterOwner->Controller))
2203 {
2204 TargetRotator.Pitch = 0.f;
2205 TargetRotator.Roll = 0.f;
2206 MoveUpdatedComponent(FVector::ZeroVector, TargetRotator, false);
2207 FinalQuat = UpdatedComponent->GetComponentQuat();
2208 }
2209 }
2210
2211 // Pipe through ControlRotation, to affect camera.
2212 if (CharacterOwner->Controller)
2213 {
2214 const FQuat PawnDeltaRotation = FinalQuat * PawnOldQuat.Inverse();
2215 FRotator FinalRotation = FinalQuat.Rotator();
2216 UpdateBasedRotation(FinalRotation, PawnDeltaRotation.Rotator());
2217 FinalQuat = UpdatedComponent->GetComponentQuat();
2218 }
2219 }
2220
2221 // We need to offset the base of the character here, not its origin, so offset by half height
2222 float HalfHeight, Radius;
2223 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(Radius, HalfHeight);
2224
2225 FVector const BaseOffset(0.0f, 0.0f, 0.0f);//(0.0f, 0.0f, HalfHeight);
2226 FVector const LocalBasePos = OldLocalToWorld.InverseTransformPosition(UpdatedComponent->GetComponentLocation() - BaseOffset);
2227 FVector const NewWorldPos = ConstrainLocationToPlane(NewLocalToWorld.TransformPosition(LocalBasePos) + BaseOffset);
2228 DeltaPosition = ConstrainDirectionToPlane(NewWorldPos - UpdatedComponent->GetComponentLocation());
2229
2230 // move attached actor
2231 if (bFastAttachedMove)
2232 {
2233 // we're trusting no other obstacle can prevent the move here
2234 UpdatedComponent->SetWorldLocationAndRotation(NewWorldPos, FinalQuat, false);
2235 }
2236 else
2237 {
2238 // hack - transforms between local and world space introducing slight error FIXMESTEVE - discuss with engine team: just skip the transforms if no rotation?
2239 FVector BaseMoveDelta = NewBaseLocation - OldBaseLocation;
2240 if (!bRotationChanged && (BaseMoveDelta.X == 0.f) && (BaseMoveDelta.Y == 0.f))
2241 {
2242 DeltaPosition.X = 0.f;
2243 DeltaPosition.Y = 0.f;
2244 }
2245
2246 FHitResult MoveOnBaseHit(1.f);
2247 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
2248 MoveUpdatedComponent(DeltaPosition, FinalQuat, true, &MoveOnBaseHit);
2249 if ((UpdatedComponent->GetComponentLocation() - (OldLocation + DeltaPosition)).IsNearlyZero() == false)
2250 {
2251 OnUnableToFollowBaseMove(DeltaPosition, OldLocation, MoveOnBaseHit);
2252 }
2253 }
2254 }
2255
2256 if (MovementBase->IsSimulatingPhysics() && CharacterOwner->GetMesh())
2257 {
2258 CharacterOwner->GetMesh()->ApplyDeltaToAllPhysicsTransforms(DeltaPosition, DeltaQuat);
2259 }
2260 }
2261}
2262
2264{
2265 FVector Result = FVector::ZeroVector;
2266
2267 if (CharacterOwner)
2268 {
2269 UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase();
2270 if (MovementBaseUtility::IsDynamicBase(MovementBase))
2271 {
2272 FVector BaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName);
2273
2274 if (bImpartBaseAngularVelocity)
2275 {
2276 // Base position should be the bottom of the actor since I offset the capsule now
2277 const FVector CharacterBasePosition = (UpdatedComponent->GetComponentLocation()/* - FVector(0.f, 0.f, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight())*/);
2278 const FVector BaseTangentialVel = MovementBaseUtility::GetMovementBaseTangentialVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName, CharacterBasePosition);
2279 BaseVelocity += BaseTangentialVel;
2280 }
2281
2282 if (bImpartBaseVelocityX)
2283 {
2284 Result.X = BaseVelocity.X;
2285 }
2286 if (bImpartBaseVelocityY)
2287 {
2288 Result.Y = BaseVelocity.Y;
2289 }
2290 if (bImpartBaseVelocityZ)
2291 {
2292 Result.Z = BaseVelocity.Z;
2293 }
2294 }
2295 }
2296
2297 return Result;
2298}
2299
2300
2301
2302void UVRCharacterMovementComponent::FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult, bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult) const
2303{
2304 SCOPE_CYCLE_COUNTER(STAT_CharFindFloor);
2305 //UE_LOG(LogVRCharacterMovement, Warning, TEXT("Find Floor"));
2306 // No collision, no floor...
2307 if (!HasValidData() || !UpdatedComponent->IsQueryCollisionEnabled())
2308 {
2309 OutFloorResult.Clear();
2310 return;
2311 }
2312
2313 //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("[Role:%d] FindFloor: %s at location %s"), (int32)CharacterOwner->Role, *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString());
2314 check(CharacterOwner->GetCapsuleComponent());
2315
2316 FVector UseCapsuleLocation = CapsuleLocation;
2317 if (VRRootCapsule)
2318 UseCapsuleLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
2319
2320 // Increase height check slightly if walking, to prevent floor height adjustment from later invalidating the floor result.
2321 const float HeightCheckAdjust = ((IsMovingOnGround() || IsClimbing()) ? MAX_FLOOR_DIST + KINDA_SMALL_NUMBER : -MAX_FLOOR_DIST);
2322
2323 float FloorSweepTraceDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust);
2324 float FloorLineTraceDist = FloorSweepTraceDist;
2325 bool bNeedToValidateFloor = true;
2326
2327 // For reverting
2328 FFindFloorResult LastFloor = CurrentFloor;
2329
2330 // Sweep floor
2331 if (FloorLineTraceDist > 0.f || FloorSweepTraceDist > 0.f)
2332 {
2334
2335 if (bAlwaysCheckFloor || !bCanUseCachedLocation || bForceNextFloorCheck || bJustTeleported)
2336 {
2337 MutableThis->bForceNextFloorCheck = false;
2338 ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult);
2339 }
2340 else
2341 {
2342 // Force floor check if base has collision disabled or if it does not block us.
2343 UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase();
2344 const AActor* BaseActor = MovementBase ? MovementBase->GetOwner() : NULL;
2345
2346 const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType();
2347
2348 if (MovementBase != NULL)
2349 {
2350 MutableThis->bForceNextFloorCheck = !MovementBase->IsQueryCollisionEnabled()
2351 || MovementBase->GetCollisionResponseToChannel(CollisionChannel) != ECR_Block
2352 || MovementBaseUtility::IsDynamicBase(MovementBase);
2353 }
2354
2355 const bool IsActorBasePendingKill = BaseActor && BaseActor->IsPendingKill();
2356
2357 if (!bForceNextFloorCheck && !IsActorBasePendingKill && MovementBase)
2358 {
2359 //UE_LOG(LogVRCharacterMovement, Log, TEXT("%s SKIP check for floor"), *CharacterOwner->GetName());
2360 OutFloorResult = CurrentFloor;
2361 bNeedToValidateFloor = false;
2362 }
2363 else
2364 {
2365 MutableThis->bForceNextFloorCheck = false;
2366 ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult);
2367 }
2368 }
2369 }
2370
2371 // #TODO: Modify the floor compute floor distance instead? Or just run movement logic differently for the bWalkingCollisionOverride setup.
2372 // #VR Specific - ignore floor traces that are negative, this can be caused by a failed floor check while starting in penetration
2373 if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && OutFloorResult.bBlockingHit && OutFloorResult.FloorDist <= 0.0f)
2374 {
2375
2376 if (OutFloorResult.FloorDist <= -FMath::Max(MAX_FLOOR_DIST, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius()))
2377 {
2378 // This was a negative trace result, the game wants us to pull out of penetration
2379 // But with walking collision override we don't want to do that, so check for the correct channel and throw away
2380 // the new floor if it matches
2381 ECollisionResponse FloorResponse;
2382 if (OutFloorResult.HitResult.Component.IsValid())
2383 {
2384 FloorResponse = OutFloorResult.HitResult.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride);
2385 if (FloorResponse == ECR_Ignore || FloorResponse == ECR_Overlap)
2386 OutFloorResult = LastFloor; // Move back to the last floor value, we are in penetration with a walking override
2387 }
2388 }
2389 }
2390
2391 // OutFloorResult.HitResult is now the result of the vertical floor check.
2392 // See if we should try to "perch" at this location.
2393 if (bNeedToValidateFloor && OutFloorResult.bBlockingHit && !OutFloorResult.bLineTrace)
2394 {
2395 const bool bCheckRadius = true;
2396 if (ShouldComputePerchResult(OutFloorResult.HitResult, bCheckRadius))
2397 {
2398 float MaxPerchFloorDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust);
2399 if (IsMovingOnGround() || IsClimbing())
2400 {
2401 MaxPerchFloorDist += FMath::Max(0.f, PerchAdditionalHeight);
2402 }
2403
2404 FFindFloorResult PerchFloorResult;
2405 if (ComputePerchResult(GetValidPerchRadius(), OutFloorResult.HitResult, MaxPerchFloorDist, PerchFloorResult))
2406 {
2407 // Don't allow the floor distance adjustment to push us up too high, or we will move beyond the perch distance and fall next time.
2408 const float AvgFloorDist = (MIN_FLOOR_DIST + MAX_FLOOR_DIST) * 0.5f;
2409 const float MoveUpDist = (AvgFloorDist - OutFloorResult.FloorDist);
2410 if (MoveUpDist + PerchFloorResult.FloorDist >= MaxPerchFloorDist)
2411 {
2412 OutFloorResult.FloorDist = AvgFloorDist;
2413 }
2414
2415 // If the regular capsule is on an unwalkable surface but the perched one would allow us to stand, override the normal to be one that is walkable.
2416 if (!OutFloorResult.bWalkableFloor)
2417 {
2418 OutFloorResult.SetFromLineTrace(PerchFloorResult.HitResult, OutFloorResult.FloorDist, FMath::Max(OutFloorResult.FloorDist, MIN_FLOOR_DIST), true);
2419 }
2420 }
2421 else
2422 {
2423 // We had no floor (or an invalid one because it was unwalkable), and couldn't perch here, so invalidate floor (which will cause us to start falling).
2424 OutFloorResult.bWalkableFloor = false;
2425 }
2426 }
2427 }
2428}
2429
2431{
2432 float depth = 0.f;
2433
2434 if (CharacterOwner && GetPhysicsVolume()->bWaterVolume)
2435 {
2436 const float CollisionHalfHeight = CharacterOwner->GetSimpleCollisionHalfHeight();
2437
2438 if ((CollisionHalfHeight == 0.f) || (Buoyancy == 0.f))
2439 {
2440 depth = 1.f;
2441 }
2442 else
2443 {
2444 UBrushComponent* VolumeBrushComp = GetPhysicsVolume()->GetBrushComponent();
2445 FHitResult Hit(1.f);
2446 if (VolumeBrushComp)
2447 {
2448 FVector TraceStart;
2449 FVector TraceEnd;
2450
2451 if (VRRootCapsule)
2452 {
2453 TraceStart = VRRootCapsule->OffsetComponentToWorld.GetLocation() + FVector(0.f, 0.f, CollisionHalfHeight);
2454 TraceEnd = VRRootCapsule->OffsetComponentToWorld.GetLocation() - FVector(0.f, 0.f, CollisionHalfHeight);
2455 }
2456 else
2457 {
2458 TraceStart = UpdatedComponent->GetComponentLocation() + FVector(0.f, 0.f, CollisionHalfHeight);
2459 TraceEnd = UpdatedComponent->GetComponentLocation() -FVector(0.f, 0.f, CollisionHalfHeight);
2460 }
2461
2462 FCollisionQueryParams NewTraceParams(CharacterMovementComponentStatics::ImmersionDepthName, true);
2463 VolumeBrushComp->LineTraceComponent(Hit, TraceStart, TraceEnd, NewTraceParams);
2464 }
2465
2466 depth = (Hit.Time == 1.f) ? 1.f : (1.f - Hit.Time);
2467 }
2468 }
2469 return depth;
2470}
2471
2473// Navigation Functions
2475
2477{
2478 SetNavWalkingPhysics(false);
2479
2480 bool bCanTeleport = true;
2481 if (CharacterOwner)
2482 {
2483 FVector CollisionFreeLocation;
2484 if (VRRootCapsule)
2485 CollisionFreeLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
2486 else
2487 CollisionFreeLocation = UpdatedComponent->GetComponentLocation();
2488
2489 // Think I need to create a custom "FindTeleportSpot" function, it is using ComponentToWorld location
2490 bCanTeleport = GetWorld()->FindTeleportSpot(CharacterOwner, CollisionFreeLocation, UpdatedComponent->GetComponentRotation());
2491 if (bCanTeleport)
2492 {
2493
2494 if (VRRootCapsule)
2495 {
2496 // Technically the same actor but i am keepign the usage convention for clarity.
2497 // Subtracting actor location from capsule to get difference in worldspace, then removing from collision free location
2498 // So that it uses the correct location.
2499 CharacterOwner->SetActorLocation(CollisionFreeLocation - (VRRootCapsule->OffsetComponentToWorld.GetLocation() - UpdatedComponent->GetComponentLocation()));
2500 }
2501 else
2502 CharacterOwner->SetActorLocation(CollisionFreeLocation);
2503 }
2504 else
2505 {
2506 SetNavWalkingPhysics(true);
2507 }
2508 }
2509
2510 bWantsToLeaveNavWalking = !bCanTeleport;
2511 return bCanTeleport;
2512}
2513
2514void UVRCharacterMovementComponent::PhysFlying(float deltaTime, int32 Iterations)
2515{
2516 if (deltaTime < MIN_TICK_TIME)
2517 {
2518 return;
2519 }
2520
2521 // Rewind the players position by the new capsule location
2523
2524 RestorePreAdditiveRootMotionVelocity();
2525 //RestorePreAdditiveVRMotionVelocity();
2526
2527 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
2528 {
2529 if (bCheatFlying && Acceleration.IsZero())
2530 {
2531 Velocity = FVector::ZeroVector;
2532 }
2533 const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction;
2534 CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration());
2535 }
2536
2537 ApplyRootMotionToVelocity(deltaTime);
2538 //ApplyVRMotionToVelocity(deltaTime);
2539
2540 // Manually handle the velocity setup
2542 bool bExtremeInput = false;
2543 if (LastPreAdditiveVRVelocity.SizeSquared() > FMath::Square(TrackingLossThreshold))
2544 {
2545 // Default to always holding position during flight to avoid too much velocity injection
2546 AdditionalVRInputVector = FVector::ZeroVector;
2547 LastPreAdditiveVRVelocity = FVector::ZeroVector;
2548 }
2549
2550 Iterations++;
2551 bJustTeleported = false;
2552
2553 FVector OldLocation = UpdatedComponent->GetComponentLocation();
2554 const FVector Adjusted = Velocity * deltaTime;
2555 FHitResult Hit(1.f);
2556 SafeMoveUpdatedComponent(Adjusted + AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), true, Hit);
2557
2558 if (Hit.Time < 1.f)
2559 {
2560 const FVector GravDir = FVector(0.f, 0.f, -1.f);
2561 const FVector VelDir = Velocity.GetSafeNormal();
2562 const float UpDown = GravDir | VelDir;
2563
2564 bool bSteppedUp = false;
2565 if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit))
2566 {
2567 float stepZ = UpdatedComponent->GetComponentLocation().Z;
2568 bSteppedUp = StepUp(GravDir, (Adjusted + AdditionalVRInputVector) * (1.f - Hit.Time) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, nullptr);
2569 if (bSteppedUp)
2570 {
2571 OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ);
2572 }
2573 }
2574
2575 if (!bSteppedUp)
2576 {
2577 //adjust and try again
2578 HandleImpact(Hit, deltaTime, Adjusted);
2579 SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
2580 }
2581 }
2582
2583 if (!bJustTeleported)
2584 {
2585 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
2586 {
2587 //Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) - AdditionalVRInputVector) / deltaTime;
2588 Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)) / deltaTime;
2589 }
2590
2592 }
2593}
2594
2595void UVRCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations)
2596{
2597 SCOPE_CYCLE_COUNTER(STAT_CharPhysFalling);
2598
2599 if (deltaTime < MIN_TICK_TIME)
2600 {
2601 return;
2602 }
2603
2604 FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime);
2605 FallAcceleration.Z = 0.f;
2606 const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration);
2607
2608 // Rewind the players position by the new capsule location
2610
2611 float remainingTime = deltaTime;
2612 while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations))
2613 {
2614 Iterations++;
2615 float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
2616 remainingTime -= timeTick;
2617
2618 const FVector OldLocation = UpdatedComponent->GetComponentLocation();
2619 const FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation;
2620
2621 const FQuat PawnRotation = UpdatedComponent->GetComponentQuat();
2622 bJustTeleported = false;
2623
2624 RestorePreAdditiveRootMotionVelocity();
2625 // RestorePreAdditiveVRMotionVelocity();
2626
2627 const FVector OldVelocity = Velocity;
2628
2629 // Apply input
2630 const float MaxDecel = GetMaxBrakingDeceleration();
2631 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
2632 {
2633 // Compute Velocity
2634 {
2635 // Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it.
2636 TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration);
2637 Velocity.Z = 0.f;
2638 CalcVelocity(timeTick, FallingLateralFriction, false, BrakingDecelerationFalling);
2639 Velocity.Z = OldVelocity.Z;
2640 }
2641 }
2642
2643 //Velocity += CustomVRInputVector / deltaTime;
2644
2645 // Compute current gravity
2646 const FVector Gravity(0.f, 0.f, GetGravityZ());
2647
2648 float GravityTime = timeTick;
2649
2650 // If jump is providing force, gravity may be affected.
2651 bool bEndingJumpForce = false;
2652 if (CharacterOwner->JumpForceTimeRemaining > 0.0f)
2653 {
2654 // Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false.
2655 const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick);
2656 GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime);
2657
2658 // Update Character state
2659 CharacterOwner->JumpForceTimeRemaining -= JumpForceTime;
2660 if (CharacterOwner->JumpForceTimeRemaining <= 0.0f)
2661 {
2662 CharacterOwner->ResetJumpState();
2663 bEndingJumpForce = true;
2664 }
2665 }
2666
2667 // Apply gravity
2668 Velocity = NewFallVelocity(Velocity, Gravity, GravityTime);
2669
2670 // See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies.
2671 static const auto CVarForceJumpPeakSubstep = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ForceJumpPeakSubstep"));
2672 if (CVarForceJumpPeakSubstep->GetInt() != 0 && OldVelocity.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation)
2673 {
2674 const FVector DerivedAccel = (Velocity - OldVelocity) / timeTick;
2675 if (!FMath::IsNearlyZero(DerivedAccel.Z))
2676 {
2677 const float TimeToApex = -OldVelocity.Z / DerivedAccel.Z;
2678
2679 // The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work.
2680 const float ApexTimeMinimum = 0.0001f;
2681 if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick)
2682 {
2683 const FVector ApexVelocity = OldVelocity + DerivedAccel * TimeToApex;
2684 Velocity = ApexVelocity;
2685 Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent.
2686
2687 // We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration.
2688 remainingTime += (timeTick - TimeToApex);
2689 timeTick = TimeToApex;
2690 Iterations--;
2691 NumJumpApexAttempts++;
2692 }
2693 }
2694 }
2695
2696 //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *Velocity.ToString());
2697
2698 ApplyRootMotionToVelocity(timeTick);
2699 //ApplyVRMotionToVelocity(deltaTime);
2700
2701 if (bNotifyApex && (Velocity.Z < 0.f))
2702 {
2703 // Just passed jump apex since now going down
2704 bNotifyApex = false;
2705 NotifyJumpApex();
2706 }
2707
2708 // Compute change in position (using midpoint integration method).
2709 FVector Adjusted = (0.5f * (OldVelocity + Velocity) * timeTick) + ((AdditionalVRInputVector / deltaTime) * timeTick);
2710
2711 ApplyVRMotionToVelocity(deltaTime);
2712
2713 // Special handling if ending the jump force where we didn't apply gravity during the jump.
2714 if (bEndingJumpForce && !bApplyGravityWhileJumping)
2715 {
2716 // We had a portion of the time at constant speed then a portion with acceleration due to gravity.
2717 // Account for that here with a more correct change in position.
2718 const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime);
2719 Adjusted = ((OldVelocity * NonGravityTime) + (0.5f * (OldVelocity + Velocity) * GravityTime)) /*+ ((AdditionalVRInputVector / deltaTime) * timeTick)*/;
2720 }
2721
2722 // Move
2723 FHitResult Hit(1.f);
2724 SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit);
2725
2726 if (!HasValidData())
2727 {
2729 return;
2730 }
2731
2732 float LastMoveTimeSlice = timeTick;
2733 float subTimeTickRemaining = timeTick * (1.f - Hit.Time);
2734
2735 if (IsSwimming()) //just entered water
2736 {
2738 remainingTime += subTimeTickRemaining;
2739 StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations);
2740 return;
2741 }
2742 else if (Hit.bBlockingHit)
2743 {
2744 if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit))
2745 {
2747 remainingTime += subTimeTickRemaining;
2748 ProcessLanded(Hit, remainingTime, Iterations);
2749 return;
2750 }
2751 else
2752 {
2753 // Compute impact deflection based on final velocity, not integration step.
2754 // This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result.
2755 Adjusted = Velocity * timeTick;
2756
2757 // See if we can convert a normally invalid landing spot (based on the hit result) to a usable one.
2758 if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit))
2759 {
2760 /*const */FVector PawnLocation = UpdatedComponent->GetComponentLocation();
2761 if (VRRootCapsule)
2762 PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation();
2763
2764 FFindFloorResult FloorResult;
2765 FindFloor(PawnLocation, FloorResult, false, NULL);
2766 if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult))
2767 {
2768 //RestorePreAdditiveVRMotionVelocity();
2769 remainingTime += subTimeTickRemaining;
2770 ProcessLanded(FloorResult.HitResult, remainingTime, Iterations);
2771 return;
2772 }
2773 }
2774
2775 HandleImpact(Hit, LastMoveTimeSlice, Adjusted);
2776
2777 // If we've changed physics mode, abort.
2778 if (!HasValidData() || !IsFalling())
2779 {
2781 return;
2782 }
2783
2784 // Limit air control based on what we hit.
2785 // We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration.
2786 FVector VelocityNoAirControl = OldVelocity;
2787 FVector AirControlAccel = Acceleration;
2788 if (bHasLimitedAirControl)
2789 {
2790 // Compute VelocityNoAirControl
2791 {
2792 // Find velocity *without* acceleration.
2793 TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector);
2794 TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity);
2795 Velocity.Z = 0.f;
2796 CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel);
2797 VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z);
2798 VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime);
2799 }
2800
2801 const bool bCheckLandingSpot = false; // we already checked above.
2802 AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick;
2803 const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice;
2804 Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice;
2805 }
2806
2807 const FVector OldHitNormal = Hit.Normal;
2808 const FVector OldHitImpactNormal = Hit.ImpactNormal;
2809 FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit);
2810
2811 // Compute velocity after deflection (only gravity component for RootMotion)
2812 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported)
2813 {
2814 const FVector NewVelocity = (Delta / subTimeTickRemaining);
2815 Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
2816 }
2817
2818 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f)
2819 {
2820 // Move in deflected direction.
2821 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
2822
2823 if (Hit.bBlockingHit)
2824 {
2825 // hit second wall
2826 LastMoveTimeSlice = subTimeTickRemaining;
2827 subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time);
2828
2829
2830 if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit))
2831 {
2833 remainingTime += subTimeTickRemaining;
2834 ProcessLanded(Hit, remainingTime, Iterations);
2835 return;
2836 }
2837
2838 HandleImpact(Hit, LastMoveTimeSlice, Delta);
2839
2840 // If we've changed physics mode, abort.
2841 if (!HasValidData() || !IsFalling())
2842 {
2844 return;
2845 }
2846
2847 // Act as if there was no air control on the last move when computing new deflection.
2848 if (bHasLimitedAirControl && Hit.Normal.Z > VERTICAL_SLOPE_NORMAL_Z)
2849 {
2850 const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice;
2851 Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit);
2852 }
2853
2854 FVector PreTwoWallDelta = Delta;
2855 TwoWallAdjust(Delta, Hit, OldHitNormal);
2856
2857 // Limit air control, but allow a slide along the second wall.
2858 if (bHasLimitedAirControl)
2859 {
2860 const bool bCheckLandingSpot = false; // we already checked above.
2861 const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining;
2862
2863 // Only allow if not back in to first wall
2864 if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f)
2865 {
2866 Delta += (AirControlDeltaV * subTimeTickRemaining);
2867 }
2868 }
2869
2870 // Compute velocity after deflection (only gravity component for RootMotion)
2871 if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported)
2872 {
2873 const FVector NewVelocity = (Delta / subTimeTickRemaining);
2874 Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity;
2875 }
2876
2877 // bDitch=true means that pawn is straddling two slopes, neither of which he can stand on
2878 bool bDitch = ((OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f));
2879 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
2880 if (Hit.Time == 0.f)
2881 {
2882 // if we are stuck then try to side step
2883 FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D();
2884 if (SideDelta.IsNearlyZero())
2885 {
2886 SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal();
2887 }
2888 SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit);
2889 }
2890
2891 if (bDitch || IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit) || Hit.Time == 0.f)
2892 {
2894 remainingTime = 0.f;
2895 ProcessLanded(Hit, remainingTime, Iterations);
2896 return;
2897 }
2898 else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ())
2899 {
2900 // We might be in a virtual 'ditch' within our perch radius. This is rare.
2901 const FVector PawnLocation = UpdatedComponent->GetComponentLocation();
2902 const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z);
2903 const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D();
2904 if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick)
2905 {
2906 Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
2907 Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f);
2908 Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f);
2909 Delta = Velocity * timeTick;
2910 SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit);
2911 }
2912 }
2913 }
2914 }
2915 }
2916 }
2917 else
2918 {
2919 // We are finding the floor and adjusting here now
2920 // This matches the final falling Z up to the PhysWalking floor offset to prevent the visible hitch when going
2921 // From falling to walking.
2922 FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false, NULL);
2923
2924 if (CurrentFloor.IsWalkableFloor())
2925 {
2926 // If the current floor distance is within the physwalking required floor offset
2927 if (CurrentFloor.GetDistanceToFloor() < (MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2)
2928 {
2929 // Adjust to correct height
2930 AdjustFloorHeight();
2931
2932 SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
2933
2934 // If this is a valid landing spot, stop falling now so that we land correctly
2935 if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, CurrentFloor.HitResult))
2936 {
2937 remainingTime += subTimeTickRemaining;
2938 ProcessLanded(CurrentFloor.HitResult, remainingTime, Iterations);
2939 return;
2940 }
2941 }
2942 }
2943 else if (CurrentFloor.HitResult.bStartPenetrating)
2944 {
2945 // The floor check failed because it started in penetration
2946 // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor.
2947 FHitResult Hitt(CurrentFloor.HitResult);
2948 Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST);
2949 const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit);
2950 ResolvePenetration(RequestedAdjustment, Hitt, UpdatedComponent->GetComponentQuat());
2951 bForceNextFloorCheck = true;
2952 }
2953 }
2954
2955 if (Velocity.SizeSquared2D() <= KINDA_SMALL_NUMBER * 10.f)
2956 {
2957 Velocity.X = 0.f;
2958 Velocity.Y = 0.f;
2959 }
2960
2962 }
2963}
2964
2965
2966void UVRCharacterMovementComponent::PhysNavWalking(float deltaTime, int32 Iterations)
2967{
2968 SCOPE_CYCLE_COUNTER(STAT_CharPhysNavWalking);
2969
2970 if (deltaTime < MIN_TICK_TIME)
2971 {
2972 return;
2973 }
2974
2975 // Root motion not for VR
2976 if ((!CharacterOwner || !CharacterOwner->Controller) && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
2977 {
2978 Acceleration = FVector::ZeroVector;
2979 Velocity = FVector::ZeroVector;
2980 return;
2981 }
2982
2983 // Rewind the players position by the new capsule location
2985
2986 RestorePreAdditiveRootMotionVelocity();
2987 //RestorePreAdditiveVRMotionVelocity();
2988
2989 // Ensure velocity is horizontal.
2990 MaintainHorizontalGroundVelocity();
2991 devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN before CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
2992
2993 //bound acceleration
2994 Acceleration.Z = 0.f;
2995 //if (!HasRootMotion())
2996 //{
2997 CalcVelocity(deltaTime, GroundFriction, false, BrakingDecelerationWalking);
2998 devCodeVR(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
2999 //}
3000
3001 ApplyRootMotionToVelocity(deltaTime);
3002 ApplyVRMotionToVelocity(deltaTime);
3003
3004 /*if (IsFalling())
3005 {
3006 // Root motion could have put us into Falling
3007 StartNewPhysics(deltaTime, Iterations);
3008 return;
3009 }*/
3010
3011 Iterations++;
3012
3013 FVector DesiredMove = Velocity;
3014 DesiredMove.Z = 0.f;
3015
3016 //const FVector OldPlayerLocation = GetActorFeetLocation();
3017 const FVector OldLocation = GetActorFeetLocationVR();
3018 const FVector DeltaMove = DesiredMove * deltaTime;
3019 const bool bDeltaMoveNearlyZero = DeltaMove.IsNearlyZero();
3020
3021 FVector AdjustedDest = OldLocation + DeltaMove;
3022 FNavLocation DestNavLocation;
3023
3024 bool bSameNavLocation = false;
3025 if (CachedNavLocation.NodeRef != INVALID_NAVNODEREF)
3026 {
3027 if (bProjectNavMeshWalking)
3028 {
3029 const float DistSq2D = (OldLocation - CachedNavLocation.Location).SizeSquared2D();
3030 const float DistZ = FMath::Abs(OldLocation.Z - CachedNavLocation.Location.Z);
3031
3032 const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f;
3033 const float ProjectionScale = (OldLocation.Z > CachedNavLocation.Location.Z) ? NavMeshProjectionHeightScaleUp : NavMeshProjectionHeightScaleDown;
3034 const float DistZThr = TotalCapsuleHeight * FMath::Max(0.f, ProjectionScale);
3035
3036 bSameNavLocation = (DistSq2D <= KINDA_SMALL_NUMBER) && (DistZ < DistZThr);
3037 }
3038 else
3039 {
3040 bSameNavLocation = CachedNavLocation.Location.Equals(OldLocation);
3041 }
3042
3043 if (bDeltaMoveNearlyZero && bSameNavLocation)
3044 {
3045 if (const INavigationDataInterface * NavData = GetNavData())
3046 {
3047 if (!NavData->IsNodeRefValid(CachedNavLocation.NodeRef))
3048 {
3049 CachedNavLocation.NodeRef = INVALID_NAVNODEREF;
3050 bSameNavLocation = false;
3051 }
3052 }
3053 }
3054 }
3055
3056 if (bDeltaMoveNearlyZero && bSameNavLocation)
3057 {
3058 DestNavLocation = CachedNavLocation;
3059 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("%s using cached navmesh location! (bProjectNavMeshWalking = %d)"), *GetNameSafe(CharacterOwner), bProjectNavMeshWalking);
3060 }
3061 else
3062 {
3063 SCOPE_CYCLE_COUNTER(STAT_CharNavProjectPoint);
3064
3065 // Start the trace from the Z location of the last valid trace.
3066 // Otherwise if we are projecting our location to the underlying geometry and it's far above or below the navmesh,
3067 // we'll follow that geometry's plane out of range of valid navigation.
3068 if (bSameNavLocation && bProjectNavMeshWalking)
3069 {
3070 AdjustedDest.Z = CachedNavLocation.Location.Z;
3071 }
3072
3073 // Find the point on the NavMesh
3074 const bool bHasNavigationData = FindNavFloor(AdjustedDest, DestNavLocation);
3075 if (!bHasNavigationData)
3076 {
3078 SetMovementMode(MOVE_Walking);
3079 return;
3080 }
3081
3082 CachedNavLocation = DestNavLocation;
3083 }
3084
3085 if (DestNavLocation.NodeRef != INVALID_NAVNODEREF)
3086 {
3087 FVector NewLocation(AdjustedDest.X, AdjustedDest.Y, DestNavLocation.Location.Z);
3088 if (bProjectNavMeshWalking)
3089 {
3090 SCOPE_CYCLE_COUNTER(STAT_CharNavProjectLocation);
3091 const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f;
3092 const float UpOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleUp);
3093 const float DownOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleDown);
3094 NewLocation = ProjectLocationFromNavMesh(deltaTime, OldLocation, NewLocation, UpOffset, DownOffset);
3095 }
3096
3097 FVector AdjustedDelta = NewLocation - OldLocation;
3098
3099 if (!AdjustedDelta.IsNearlyZero())
3100 {
3101 // 4.16 UNCOMMENT
3102 FHitResult HitResult;
3103 SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweepWhileNavWalking, HitResult);
3104
3105 /* 4.16 Delete*/
3106 //const bool bSweep = UpdatedPrimitive ? UpdatedPrimitive->bGenerateOverlapEvents : false;
3107 //FHitResult HitResult;
3108 //SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweep, HitResult);
3109 // End 4.16 delete
3110 }
3111
3112 // Update velocity to reflect actual move
3113 if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasVelocity())
3114 {
3115 Velocity = (GetActorFeetLocationVR() - OldLocation) / deltaTime;
3116 MaintainHorizontalGroundVelocity();
3117 }
3118
3119 bJustTeleported = false;
3120 }
3121 else
3122 {
3123 StartFalling(Iterations, deltaTime, deltaTime, DeltaMove, OldLocation);
3124 }
3125
3127}
3128
3129void UVRCharacterMovementComponent::PhysSwimming(float deltaTime, int32 Iterations)
3130{
3131 if (deltaTime < MIN_TICK_TIME)
3132 {
3133 return;
3134 }
3135
3136 // Rewind the players position by the new capsule location
3138
3139 RestorePreAdditiveRootMotionVelocity();
3140 //RestorePreAdditiveVRMotionVelocity();
3141
3142 float NetFluidFriction = 0.f;
3143 float Depth = ImmersionDepth();
3144 float NetBuoyancy = Buoyancy * Depth;
3145 float OriginalAccelZ = Acceleration.Z;
3146 bool bLimitedUpAccel = false;
3147
3148 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 0.33f * MaxSwimSpeed) && (NetBuoyancy != 0.f))
3149 {
3150 //damp positive Z out of water
3151 Velocity.Z = FMath::Max(0.33f * MaxSwimSpeed, Velocity.Z * Depth*Depth);
3152 }
3153 else if (Depth < 0.65f)
3154 {
3155 bLimitedUpAccel = (Acceleration.Z > 0.f);
3156 Acceleration.Z = FMath::Min(0.1f, Acceleration.Z);
3157 }
3158
3159 Iterations++;
3160 FVector OldLocation = UpdatedComponent->GetComponentLocation();
3161 bJustTeleported = false;
3162 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
3163 {
3164 const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction * Depth;
3165 CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration());
3166 Velocity.Z += GetGravityZ() * deltaTime * (1.f - NetBuoyancy);
3167 }
3168
3169 ApplyRootMotionToVelocity(deltaTime);
3170 ApplyVRMotionToVelocity(deltaTime);
3171
3172 FVector Adjusted = Velocity * deltaTime;
3173 FHitResult Hit(1.f);
3174 float remainingTime = deltaTime * SwimVR(Adjusted/* + AdditionalVRInputVector*/, Hit);
3175
3176 //may have left water - if so, script might have set new physics mode
3177 if (!IsSwimming())
3178 {
3180 StartNewPhysics(remainingTime, Iterations);
3181 return;
3182 }
3183
3184 if (Hit.Time < 1.f && CharacterOwner)
3185 {
3186 HandleSwimmingWallHit(Hit, deltaTime);
3187 if (bLimitedUpAccel && (Velocity.Z >= 0.f))
3188 {
3189 // allow upward velocity at surface if against obstacle
3190 Velocity.Z += OriginalAccelZ * deltaTime;
3191 Adjusted = Velocity * (1.f - Hit.Time)*deltaTime;
3192 SwimVR(Adjusted, Hit);
3193 if (!IsSwimming())
3194 {
3196 StartNewPhysics(remainingTime, Iterations);
3197 return;
3198 }
3199 }
3200
3201 const FVector GravDir = FVector(0.f, 0.f, -1.f);
3202 const FVector VelDir = Velocity.GetSafeNormal();
3203 const float UpDown = GravDir | VelDir;
3204
3205 bool bSteppedUp = false;
3206 if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit))
3207 {
3208 float stepZ = UpdatedComponent->GetComponentLocation().Z;
3209 const FVector RealVelocity = Velocity;
3210 Velocity.Z = 1.f; // HACK: since will be moving up, in case pawn leaves the water
3211 bSteppedUp = StepUp(GravDir, (Adjusted/* + AdditionalVRInputVector*/) * (1.f - Hit.Time), Hit);
3212 if (bSteppedUp)
3213 {
3214 //may have left water - if so, script might have set new physics mode
3215 if (!IsSwimming())
3216 {
3218 StartNewPhysics(remainingTime, Iterations);
3219 return;
3220 }
3221 OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ);
3222 }
3223 Velocity = RealVelocity;
3224 }
3225
3226 if (!bSteppedUp)
3227 {
3228 //adjust and try again
3229 HandleImpact(Hit, deltaTime, Adjusted);
3230 SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
3231 }
3232 }
3233
3234 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported && ((deltaTime - remainingTime) > KINDA_SMALL_NUMBER) && CharacterOwner)
3235 {
3236 bool bWaterJump = !GetPhysicsVolume()->bWaterVolume;
3237 float velZ = Velocity.Z;
3238 Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)/* - AdditionalVRInputVector*/) / (deltaTime - remainingTime);
3239 if (bWaterJump)
3240 {
3241 Velocity.Z = velZ;
3242 }
3243 }
3244
3245 if (!GetPhysicsVolume()->bWaterVolume && IsSwimming())
3246 {
3247 SetMovementMode(MOVE_Falling); //in case script didn't change it (w/ zone change)
3248 }
3249
3251
3252 //may have left water - if so, script might have set new physics mode
3253 if (!IsSwimming())
3254 {
3255 StartNewPhysics(remainingTime, Iterations);
3256 }
3257}
3258
3259
3260void UVRCharacterMovementComponent::StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations)
3261{
3262 if (remainingTime < MIN_TICK_TIME || timeTick < MIN_TICK_TIME)
3263 {
3264 return;
3265 }
3266
3267 FVector NewLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation();
3268
3269 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported)
3270 {
3271 Velocity = (NewLocation - OldLocation) / timeTick; //actual average velocity
3272 Velocity = 2.f*Velocity - OldVelocity; //end velocity has 2* accel of avg
3273 Velocity = Velocity.GetClampedToMaxSize(GetPhysicsVolume()->TerminalVelocity);
3274 }
3275 const FVector End = FindWaterLine(NewLocation, OldLocation);
3276 float waterTime = 0.f;
3277 if (End != NewLocation)
3278 {
3279 const float ActualDist = (NewLocation - OldLocation).Size();
3280 if (ActualDist > KINDA_SMALL_NUMBER)
3281 {
3282 waterTime = timeTick * (End - NewLocation).Size() / ActualDist;
3283 remainingTime += waterTime;
3284 }
3285 MoveUpdatedComponent(End - NewLocation, UpdatedComponent->GetComponentQuat(), true);
3286 }
3287 if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 2.f*SWIMBOBSPEED) && (Velocity.Z < 0.f)) //allow for falling out of water
3288 {
3289 Velocity.Z = SWIMBOBSPEED - Velocity.Size2D() * 0.7f; //smooth bobbing
3290 }
3291 if ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations))
3292 {
3293 PhysSwimming(remainingTime, Iterations);
3294 }
3295}
3296
3297float UVRCharacterMovementComponent::SwimVR(FVector Delta, FHitResult& Hit)
3298{
3299 FVector Start = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation();
3300
3301 float airTime = 0.f;
3302 SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit);
3303
3304 if (!GetPhysicsVolume()->bWaterVolume) //then left water
3305 {
3306 FVector NewLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation();
3307
3308 const FVector End = FindWaterLine(Start, NewLoc);
3309 const float DesiredDist = Delta.Size();
3310 if (End != NewLoc && DesiredDist > KINDA_SMALL_NUMBER)
3311 {
3312 airTime = (End - NewLoc).Size() / DesiredDist;
3313 if (((NewLoc - Start) | (End - NewLoc)) > 0.f)
3314 {
3315 airTime = 0.f;
3316 }
3317 SafeMoveUpdatedComponent(End - NewLoc, UpdatedComponent->GetComponentQuat(), true, Hit);
3318 }
3319 }
3320 return airTime;
3321}
3322
3323bool UVRCharacterMovementComponent::CheckWaterJump(FVector CheckPoint, FVector& WallNormal)
3324{
3325 if (!HasValidData())
3326 {
3327 return false;
3328 }
3329 FVector currentLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation();
3330
3331 // check if there is a wall directly in front of the swimming pawn
3332 CheckPoint.Z = 0.f;
3333 FVector CheckNorm = CheckPoint.GetSafeNormal();
3334 float PawnCapsuleRadius, PawnCapsuleHalfHeight;
3335 CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnCapsuleRadius, PawnCapsuleHalfHeight);
3336 CheckPoint = currentLoc + 1.2f * PawnCapsuleRadius * CheckNorm;
3337 FVector Extent(PawnCapsuleRadius, PawnCapsuleRadius, PawnCapsuleHalfHeight);
3338 FHitResult HitInfo(1.f);
3339 FCollisionQueryParams CapsuleParams(SCENE_QUERY_STAT(CheckWaterJump), false, CharacterOwner);
3340 FCollisionResponseParams ResponseParam;
3341 InitCollisionParams(CapsuleParams, ResponseParam);
3342 FCollisionShape CapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_None);
3343 const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType();
3344 bool bHit = GetWorld()->SweepSingleByChannel(HitInfo, currentLoc, CheckPoint, FQuat::Identity, CollisionChannel, CapsuleShape, CapsuleParams, ResponseParam);
3345
3346 if (bHit && !Cast<APawn>(HitInfo.GetActor()))
3347 {
3348 // hit a wall - check if it is low enough
3349 WallNormal = -1.f * HitInfo.ImpactNormal;
3350 FVector Start = currentLoc;//UpdatedComponent->GetComponentLocation();
3351 Start.Z += MaxOutOfWaterStepHeight;
3352 CheckPoint = Start + 3.2f * PawnCapsuleRadius * WallNormal;
3353 FCollisionQueryParams LineParams(SCENE_QUERY_STAT(CheckWaterJump), true, CharacterOwner);
3354 FCollisionResponseParams LineResponseParam;
3355 InitCollisionParams(LineParams, LineResponseParam);
3356 bHit = GetWorld()->LineTraceSingleByChannel(HitInfo, Start, CheckPoint, CollisionChannel, LineParams, LineResponseParam);
3357 // if no high obstruction, or it's a valid floor, then pawn can jump out of water
3358 return !bHit || IsWalkable(HitInfo);
3359 }
3360 return false;
3361}
3362
3364{
3365 return FBasedPosition(NULL, GetActorFeetLocationVR());
3366}
3367
3368void UVRCharacterMovementComponent::ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations)
3369{
3370 SCOPE_CYCLE_COUNTER(STAT_CharProcessLanded);
3371
3372 if (CharacterOwner && CharacterOwner->ShouldNotifyLanded(Hit))
3373 {
3374 CharacterOwner->Landed(Hit);
3375 }
3376 if (IsFalling())
3377 {
3378
3379 if (GetGroundMovementMode() == MOVE_NavWalking)
3380 {
3381 // verify navmesh projection and current floor
3382 // otherwise movement will be stuck in infinite loop:
3383 // navwalking -> (no navmesh) -> falling -> (standing on something) -> navwalking -> ....
3384
3385 const FVector TestLocation = GetActorFeetLocationVR();
3386 FNavLocation NavLocation;
3387
3388 const bool bHasNavigationData = FindNavFloor(TestLocation, NavLocation);
3389 if (!bHasNavigationData || NavLocation.NodeRef == INVALID_NAVNODEREF)
3390 {
3391 SetGroundMovementMode(MOVE_Walking);
3392 //GroundMovementMode = MOVE_Walking;
3393 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("ProcessLanded(): %s tried to go to NavWalking but couldn't find NavMesh! Using Walking instead."), *GetNameSafe(CharacterOwner));
3394 }
3395 }
3396
3397 SetPostLandedPhysics(Hit);
3398 }
3399
3400 IPathFollowingAgentInterface* PFAgent = GetPathFollowingAgent();
3401 if (PFAgent)
3402 {
3403 PFAgent->OnLanded();
3404 }
3405
3406 StartNewPhysics(remainingTime, Iterations);
3407}
3408
3410// End Navigation Functions
3412
3413void UVRCharacterMovementComponent::PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction& ThisTickFunction)
3414{
3415 if (bDeferUpdateBasedMovement)
3416 {
3417 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
3418 UpdateBasedMovement(DeltaTime);
3419 SaveBaseLocation();
3420 bDeferUpdateBasedMovement = false;
3421 }
3422}
3423
3424
3426{
3427 if (!HasValidData() || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics())
3428 {
3429 return;
3430 }
3431
3432 const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy);
3433 const FRepMovement& ConstRepMovement = CharacterOwner->GetReplicatedMovement();
3434
3435 // Workaround for replication not being updated initially
3436 if (bIsSimulatedProxy &&
3437 ConstRepMovement.Location.IsZero() &&
3438 ConstRepMovement.Rotation.IsZero() &&
3439 ConstRepMovement.LinearVelocity.IsZero())
3440 {
3441 return;
3442 }
3443
3444 // If base is not resolved on the client, we should not try to simulate at all
3445 if (CharacterOwner->GetReplicatedBasedMovement().IsBaseUnresolved())
3446 {
3447 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Base for simulated character '%s' is not resolved on client, skipping SimulateMovement"), *CharacterOwner->GetName());
3448 return;
3449 }
3450
3451 FVector OldVelocity;
3452 FVector OldLocation;
3453
3454 // Scoped updates can improve performance of multiple MoveComponent calls.
3455 {
3456 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
3457
3458 bool bHandledNetUpdate = false;
3459 if (bIsSimulatedProxy)
3460 {
3461 // Handle network changes
3462 if (bNetworkUpdateReceived)
3463 {
3464 bNetworkUpdateReceived = false;
3465 bHandledNetUpdate = true;
3466 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s received net update"), *GetNameSafe(CharacterOwner));
3467 if (bNetworkMovementModeChanged)
3468 {
3469 ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode());
3470 bNetworkMovementModeChanged = false;
3471 }
3472 else if (bJustTeleported || bForceNextFloorCheck)
3473 {
3474 // Make sure floor is current. We will continue using the replicated base, if there was one.
3475 bJustTeleported = false;
3476 UpdateFloorFromAdjustment();
3477 }
3478 }
3479 else if (bForceNextFloorCheck)
3480 {
3481 UpdateFloorFromAdjustment();
3482 }
3483 }
3484
3485 UpdateCharacterStateBeforeMovement(DeltaSeconds);
3486
3487 if (MovementMode != MOVE_None)
3488 {
3489 //TODO: Also ApplyAccumulatedForces()?
3490 HandlePendingLaunch();
3491 }
3492 ClearAccumulatedForces();
3493
3494 if (MovementMode == MOVE_None)
3495 {
3496 return;
3497 }
3498
3499 const bool bSimGravityDisabled = (bIsSimulatedProxy && CharacterOwner->bSimGravityDisabled);
3500 const bool bZeroReplicatedGroundVelocity = (bIsSimulatedProxy && IsMovingOnGround() && ConstRepMovement.LinearVelocity.IsZero());
3501
3502 // bSimGravityDisabled means velocity was zero when replicated and we were stuck in something. Avoid external changes in velocity as well.
3503 // Being in ground movement with zero velocity, we cannot simulate proxy velocities safely because we might not get any further updates from the server.
3504 if (bSimGravityDisabled || bZeroReplicatedGroundVelocity)
3505 {
3506 Velocity = FVector::ZeroVector;
3507 }
3508
3509
3510 MaybeUpdateBasedMovement(DeltaSeconds);
3511
3512 // simulated pawns predict location
3513 OldVelocity = Velocity;
3514 OldLocation = UpdatedComponent->GetComponentLocation();
3515
3516 UpdateProxyAcceleration();
3517
3518 static const auto CVarNetEnableSkipProxyPredictionOnNetUpdate = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableSkipProxyPredictionOnNetUpdate"));
3519 // May only need to simulate forward on frames where we haven't just received a new position update.
3520 if (!bHandledNetUpdate || !bNetworkSkipProxyPredictionOnNetUpdate || !CVarNetEnableSkipProxyPredictionOnNetUpdate->GetInt())
3521 {
3522 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s simulating movement"), *GetNameSafe(CharacterOwner));
3523 FStepDownResult StepDownResult;
3524
3525 // Skip the estimated movement when movement simulation is off, but keep the floor find
3527 {
3528 MoveSmooth(Velocity, DeltaSeconds, &StepDownResult);
3529 }
3530
3531 // find floor and check if falling
3532 if (IsMovingOnGround() || MovementMode == MOVE_Falling)
3533 {
3534 if (StepDownResult.bComputedFloor)
3535 {
3536 CurrentFloor = StepDownResult.FloorResult;
3537 }
3538 else if (bDisableSimulatedTickWhenSmoothingMovement || Velocity.Z <= 0.f)
3539 {
3540 FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, Velocity.IsZero(), NULL);
3541 }
3542 else
3543 {
3544 CurrentFloor.Clear();
3545 }
3546
3547 if (!CurrentFloor.IsWalkableFloor())
3548 {
3549 if (!bSimGravityDisabled)
3550 {
3551 // No floor, must fall.
3552 if (Velocity.Z <= 0.f || bApplyGravityWhileJumping || !CharacterOwner->IsJumpProvidingForce())
3553 {
3554 Velocity = NewFallVelocity(Velocity, FVector(0.f, 0.f, GetGravityZ()), DeltaSeconds);
3555 }
3556 }
3557 SetMovementMode(MOVE_Falling);
3558 }
3559 else
3560 {
3561 // Walkable floor
3562 if (IsMovingOnGround())
3563 {
3564 AdjustFloorHeight();
3565 SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName);
3566 }
3567 else if (MovementMode == MOVE_Falling)
3568 {
3569 if (CurrentFloor.FloorDist <= MIN_FLOOR_DIST || (bSimGravityDisabled && CurrentFloor.FloorDist <= MAX_FLOOR_DIST))
3570 {
3571 // Landed
3572 SetPostLandedPhysics(CurrentFloor.HitResult);
3573 }
3574 else
3575 {
3576 if (!bSimGravityDisabled)
3577 {
3578 // Continue falling.
3579 Velocity = NewFallVelocity(Velocity, FVector(0.f, 0.f, GetGravityZ()), DeltaSeconds);
3580 }
3581 CurrentFloor.Clear();
3582 }
3583 }
3584 }
3585 }
3586 }
3587 else
3588 {
3589 UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s SKIPPING simulate movement"), *GetNameSafe(CharacterOwner));
3590 }
3591
3592 UpdateCharacterStateAfterMovement(DeltaSeconds);
3593
3594 // consume path following requested velocity
3595 bHasRequestedVelocity = false;
3596
3597 OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
3598 } // End scoped movement update
3599
3600 // Call custom post-movement events. These happen after the scoped movement completes in case the events want to use the current state of overlaps etc.
3601 CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity);
3602
3603 SaveBaseLocation();
3604 UpdateComponentVelocity();
3605 bJustTeleported = false;
3606
3607 LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector;
3608 LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity;
3609 LastUpdateVelocity = Velocity;
3610}
3611
3612void UVRCharacterMovementComponent::MoveSmooth(const FVector& InVelocity, const float DeltaSeconds, FStepDownResult* OutStepDownResult)
3613{
3614 if (!HasValidData())
3615 {
3616 return;
3617 }
3618
3619 // Custom movement mode.
3620 // Custom movement may need an update even if there is zero velocity.
3621 if (MovementMode == MOVE_Custom)
3622 {
3623 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
3624 PhysCustom(DeltaSeconds, 0);
3625 return;
3626 }
3627
3628 FVector Delta = InVelocity * DeltaSeconds;
3629 if (Delta.IsZero())
3630 {
3631 return;
3632 }
3633
3634 FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
3635
3636 if (IsMovingOnGround())
3637 {
3638 MoveAlongFloor(InVelocity, DeltaSeconds, OutStepDownResult);
3639 }
3640 else
3641 {
3642 FHitResult Hit(1.f);
3643 SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit);
3644
3645 if (Hit.IsValidBlockingHit())
3646 {
3647 bool bSteppedUp = false;
3648
3649 if (IsFlying())
3650 {
3651 if (CanStepUp(Hit))
3652 {
3653 OutStepDownResult = NULL; // No need for a floor when not walking.
3654 if (FMath::Abs(Hit.ImpactNormal.Z) < 0.2f)
3655 {
3656 const FVector GravDir = FVector(0.f, 0.f, -1.f);
3657 const FVector DesiredDir = Delta.GetSafeNormal();
3658 const float UpDown = GravDir | DesiredDir;
3659 if ((UpDown < 0.5f) && (UpDown > -0.2f))
3660 {
3661 bSteppedUp = StepUp(GravDir, Delta * (1.f - Hit.Time), Hit, OutStepDownResult);
3662 }
3663 }
3664 }
3665 }
3666
3667 // If StepUp failed, try sliding.
3668 if (!bSteppedUp)
3669 {
3670 SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, false);
3671 }
3672 }
3673 }
3674}
3675
3677{
3678 if (MoveResponse.IsGoodMove())
3679 {
3680 ClientAckGoodMove_Implementation(MoveResponse.ClientAdjustment.TimeStamp);
3681 }
3682 else
3683 {
3684 // Wrappers to old RPC handlers, to maintain compatibility. If overrides need additional serialized data, they can access GetMoveResponseDataContainer()
3685 if (MoveResponse.bRootMotionSourceCorrection)
3686 {
3687 if (FRootMotionSourceGroup* RootMotionSourceGroup = MoveResponse.GetRootMotionSourceGroup(*this))
3688 {
3689 ClientAdjustRootMotionSourcePosition_Implementation(
3690 MoveResponse.ClientAdjustment.TimeStamp,
3691 *RootMotionSourceGroup,
3692 MoveResponse.bRootMotionMontageCorrection,
3693 MoveResponse.RootMotionTrackPosition,
3694 MoveResponse.ClientAdjustment.NewLoc,
3695 MoveResponse.RootMotionRotation,
3696 MoveResponse.ClientAdjustment.NewVel.Z,
3697 MoveResponse.ClientAdjustment.NewBase,
3698 MoveResponse.ClientAdjustment.NewBaseBoneName,
3699 MoveResponse.bHasBase,
3700 MoveResponse.ClientAdjustment.bBaseRelativePosition,
3701 MoveResponse.ClientAdjustment.MovementMode);
3702 }
3703 }
3704 else if (MoveResponse.bRootMotionMontageCorrection)
3705 {
3706 ClientAdjustRootMotionPosition_Implementation(
3707 MoveResponse.ClientAdjustment.TimeStamp,
3708 MoveResponse.RootMotionTrackPosition,
3709 MoveResponse.ClientAdjustment.NewLoc,
3710 MoveResponse.RootMotionRotation,
3711 MoveResponse.ClientAdjustment.NewVel.Z,
3712 MoveResponse.ClientAdjustment.NewBase,
3713 MoveResponse.ClientAdjustment.NewBaseBoneName,
3714 MoveResponse.bHasBase,
3715 MoveResponse.ClientAdjustment.bBaseRelativePosition,
3716 MoveResponse.ClientAdjustment.MovementMode);
3717 }
3718 else
3719 {
3721 MoveResponse.ClientAdjustment.TimeStamp,
3722 MoveResponse.ClientAdjustment.NewLoc,
3723 FRotator::CompressAxisToShort(MoveResponse.ClientAdjustment.NewRot.Yaw),
3724 MoveResponse.ClientAdjustment.NewVel,
3725 MoveResponse.ClientAdjustment.NewBase,
3726 MoveResponse.ClientAdjustment.NewBaseBoneName,
3727 MoveResponse.bHasBase,
3728 MoveResponse.ClientAdjustment.bBaseRelativePosition,
3729 MoveResponse.ClientAdjustment.MovementMode);
3730 }
3731 }
3732}
3733
3735(
3736 float TimeStamp,
3737 FVector NewLocation,
3738 uint16 NewYaw,
3739 FVector NewVelocity,
3740 UPrimitiveComponent* NewBase,
3741 FName NewBaseBoneName,
3742 bool bHasBase,
3743 bool bBaseRelativePosition,
3744 uint8 ServerMovementMode
3745)
3746{
3747 if (!HasValidData() || !IsActive())
3748 {
3749 return;
3750 }
3751
3752
3753 FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character();
3754 check(ClientData);
3755
3756 // Make sure the base actor exists on this client.
3757 const bool bUnresolvedBase = bHasBase && (NewBase == NULL);
3758 if (bUnresolvedBase)
3759 {
3760 if (bBaseRelativePosition)
3761 {
3762 UE_LOG(LogNetPlayerMovement, Warning, TEXT("ClientAdjustPosition_Implementation could not resolve the new relative movement base actor, ignoring server correction! Client currently at world location %s on base %s"),
3763 *UpdatedComponent->GetComponentLocation().ToString(), *GetNameSafe(GetMovementBase()));
3764 return;
3765 }
3766 else
3767 {
3768 UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientAdjustPosition_Implementation could not resolve the new absolute movement base actor, but WILL use the position!"));
3769 }
3770 }
3771
3772 // Ack move if it has not expired.
3773 int32 MoveIndex = ClientData->GetSavedMoveIndex(TimeStamp);
3774 if (MoveIndex == INDEX_NONE)
3775 {
3776 if (ClientData->LastAckedMove.IsValid())
3777 {
3778 UE_LOG(LogNetPlayerMovement, Log, TEXT("ClientAdjustPosition_Implementation could not find Move for TimeStamp: %f, LastAckedTimeStamp: %f, CurrentTimeStamp: %f"), TimeStamp, ClientData->LastAckedMove->TimeStamp, ClientData->CurrentTimeStamp);
3779 }
3780 return;
3781 }
3782
3784 {
3785 float YawValue = FRotator::DecompressAxisFromShort(NewYaw);
3786 // Trust the server's control yaw
3787 if (ClientData->LastAckedMove.IsValid() && !FMath::IsNearlyEqual(ClientData->LastAckedMove->SavedControlRotation.Yaw, YawValue))
3788 {
3790 {
3791 if (BaseVRCharacterOwner->bUseControllerRotationYaw)
3792 {
3793 AController * myController = BaseVRCharacterOwner->GetController();
3794 if (myController)
3795 {
3796 //FRotator newRot = myController->GetControlRotation();
3797 myController->SetControlRotation(FRotator(0.f, YawValue, 0.f));//(ClientData->LastAckedMove->SavedControlRotation);
3798 }
3799 }
3800
3801 BaseVRCharacterOwner->SetActorRotation(FRotator(0.f, YawValue, 0.f));
3802 }
3803 }
3804 }
3805
3806 ClientData->AckMove(MoveIndex, *this);
3807
3808 FVector WorldShiftedNewLocation;
3809 // Received Location is relative to dynamic base
3810 if (bBaseRelativePosition)
3811 {
3812 FVector BaseLocation;
3813 FQuat BaseRotation;
3814 MovementBaseUtility::GetMovementBaseTransform(NewBase, NewBaseBoneName, BaseLocation, BaseRotation); // TODO: error handling if returns false
3815 WorldShiftedNewLocation = NewLocation + BaseLocation;
3816 }
3817 else
3818 {
3819 WorldShiftedNewLocation = FRepMovement::RebaseOntoLocalOrigin(NewLocation, this);
3820 }
3821
3822
3823 // Trigger event
3824 OnClientCorrectionReceived(*ClientData, TimeStamp, WorldShiftedNewLocation, NewVelocity, NewBase, NewBaseBoneName, bHasBase, bBaseRelativePosition, ServerMovementMode);
3825
3826 // Trust the server's positioning.
3827 if (UpdatedComponent)
3828 {
3830 {
3831 /*if (!bUseClientControlRotation)
3832 {
3833 float YawValue = FRotator::DecompressAxisFromShort(NewYaw);
3834 BaseVRCharacterOwner->SetActorLocationAndRotationVR(WorldShiftedNewLocation, FRotator(0.0f, YawValue, 0.0f), true, true, true);
3835 }
3836 else*/
3837 {
3838 BaseVRCharacterOwner->SetActorLocationVR(WorldShiftedNewLocation, true);
3839 }
3840 }
3841 else
3842 {
3843 UpdatedComponent->SetWorldLocation(WorldShiftedNewLocation, false, nullptr, ETeleportType::TeleportPhysics);
3844 }
3845 }
3846
3847 Velocity = NewVelocity;
3848
3849 // Trust the server's movement mode
3850 UPrimitiveComponent* PreviousBase = CharacterOwner->GetMovementBase();
3851 ApplyNetworkMovementMode(ServerMovementMode);
3852
3853 // Set base component
3854 UPrimitiveComponent* FinalBase = NewBase;
3855 FName FinalBaseBoneName = NewBaseBoneName;
3856 if (bUnresolvedBase)
3857 {
3858 check(NewBase == NULL);
3859 check(!bBaseRelativePosition);
3860
3861 // We had an unresolved base from the server
3862 // If walking, we'd like to continue walking if possible, to avoid falling for a frame, so try to find a base where we moved to.
3863 if (PreviousBase && UpdatedComponent)
3864 {
3865 FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false);
3866 if (CurrentFloor.IsWalkableFloor())
3867 {
3868 FinalBase = CurrentFloor.HitResult.Component.Get();
3869 FinalBaseBoneName = CurrentFloor.HitResult.BoneName;
3870 }
3871 else
3872 {
3873 FinalBase = nullptr;
3874 FinalBaseBoneName = NAME_None;
3875 }
3876 }
3877 }
3878 SetBase(FinalBase, FinalBaseBoneName);
3879
3880 // Update floor at new location
3881 UpdateFloorFromAdjustment();
3882 bJustTeleported = true;
3883
3884 // Even if base has not changed, we need to recompute the relative offsets (since we've moved).
3885 SaveBaseLocation();
3886
3887 LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector;
3888 LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity;
3889 LastUpdateVelocity = Velocity;
3890
3891 UpdateComponentVelocity();
3892 ClientData->bUpdatePosition = true;
3893}
3894
3895bool UVRCharacterMovementComponent::ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& ClientWorldLocation, float ClientYaw, const FVector& RelativeClientLocation, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode)
3896{
3897 // Check location difference against global setting
3898 if (!bIgnoreClientMovementErrorChecksAndCorrection)
3899 {
3900 //const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation;
3901
3902#if ROOT_MOTION_DEBUG
3903 if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnAnyThread() == 1)
3904 {
3905 const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation;
3906 FString AdjustedDebugString = FString::Printf(TEXT("ServerCheckClientError LocDiff(%.1f) ExceedsAllowablePositionError(%d) TimeStamp(%f)"),
3907 LocDiff.Size(), GetDefault<AGameNetworkManager>()->ExceedsAllowablePositionError(LocDiff), ClientTimeStamp);
3908 RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
3909 }
3910#endif
3911
3912 if (ServerExceedsAllowablePositionError(ClientTimeStamp, DeltaTime, Accel, ClientWorldLocation, RelativeClientLocation, ClientMovementBase, ClientBaseBoneName, ClientMovementMode))
3913 {
3914 return true;
3915 }
3916
3917#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
3918 static const auto CVarNetForceClientAdjustmentPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientAdjustmentPercent"));
3919 if (CVarNetForceClientAdjustmentPercent->GetFloat() > SMALL_NUMBER)
3920 {
3921 if (RandomStream.FRand() < CVarNetForceClientAdjustmentPercent->GetFloat())
3922 {
3923 UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("** ServerCheckClientError forced by p.NetForceClientAdjustmentPercent"));
3924 return true;
3925 }
3926 }
3927#endif
3928 }
3929 else
3930 {
3931#if !UE_BUILD_SHIPPING
3932 static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections"));
3933 if (CVarNetShowCorrections->GetInt() != 0)
3934 {
3935 UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: %s is set to ignore error checks and corrections."), *GetNameSafe(CharacterOwner));
3936 }
3937#endif // !UE_BUILD_SHIPPING
3938 }
3939
3940 // If we are rolling back client rotation
3941 if (!bUseClientControlRotation && !FMath::IsNearlyEqual(FRotator::ClampAxis(ClientYaw), FRotator::ClampAxis(UpdatedComponent->GetComponentRotation().Yaw), CharacterMovementComponentStatics::fRotationCorrectionThreshold))
3942 {
3943 return true;
3944 }
3945
3946 return false;
3947}
3948
3949void UVRCharacterMovementComponent::ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& RelativeClientLoc, float ClientYaw, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode)
3950{
3951 if (!ShouldUsePackedMovementRPCs())
3952 {
3953 if (RelativeClientLoc == FVector(1.f, 2.f, 3.f)) // first part of double servermove
3954 {
3955 return;
3956 }
3957 }
3958
3959 FNetworkPredictionData_Server_VRCharacter* ServerData = ((FNetworkPredictionData_Server_VRCharacter*)GetPredictionData_Server_Character());
3960 check(ServerData);
3961
3962 // Don't prevent more recent updates from being sent if received this frame.
3963 // We're going to send out an update anyway, might as well be the most recent one.
3964 APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController());
3965 if ((ServerData->LastUpdateTime != GetWorld()->TimeSeconds))
3966 {
3967 const AGameNetworkManager* GameNetworkManager = (const AGameNetworkManager*)(AGameNetworkManager::StaticClass()->GetDefaultObject());
3968 if (GameNetworkManager->WithinUpdateDelayBounds(PC, ServerData->LastUpdateTime))
3969 {
3970 return;
3971 }
3972 }
3973
3974 // Offset may be relative to base component
3975 FVector ClientLoc = RelativeClientLoc;
3976 if (MovementBaseUtility::UseRelativeLocation(ClientMovementBase))
3977 {
3978 FVector BaseLocation;
3979 FQuat BaseRotation;
3980 MovementBaseUtility::GetMovementBaseTransform(ClientMovementBase, ClientBaseBoneName, BaseLocation, BaseRotation);
3981 ClientLoc += BaseLocation;
3982 }
3983 else
3984 {
3985 ClientLoc = FRepMovement::RebaseOntoLocalOrigin(ClientLoc, this);
3986 }
3987
3988 // Client may send a null movement base when walking on bases with no relative location (to save bandwidth).
3989 // In this case don't check movement base in error conditions, use the server one (which avoids an error based on differing bases). Position will still be validated.
3990 if (ClientMovementBase == nullptr && ClientMovementMode == MOVE_Walking)
3991 {
3992 ClientMovementBase = CharacterOwner->GetBasedMovement().MovementBase;
3993 ClientBaseBoneName = CharacterOwner->GetBasedMovement().BoneName;
3994 }
3995
3996 bool bInClientAuthoritativeMovementMode = false;
3997 // Custom movement modes aren't going to be rolled back as they are client authed for our pawns
3998
3999 TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None);
4000 TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None);
4001 uint8 NetCustomMode(0);
4002 UnpackNetworkMovementMode(ClientMovementMode, NetMovementMode, NetCustomMode, NetGroundMode);
4003 if (NetMovementMode == EMovementMode::MOVE_Custom)
4004 {
4005 if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)
4006 bInClientAuthoritativeMovementMode = true;
4007 }
4008
4009 // Compute the client error from the server's position
4010 // If client has accumulated a noticeable positional error, correct them.
4011 bNetworkLargeClientCorrection = ServerData->bForceClientUpdate;
4012
4013 if (!bInClientAuthoritativeMovementMode && (ServerData->bForceClientUpdate || ServerCheckClientErrorVR(ClientTimeStamp, DeltaTime, Accel, ClientLoc, ClientYaw, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)))
4014 {
4015 UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase();
4016 ServerData->PendingAdjustment.NewVel = Velocity;
4017 ServerData->PendingAdjustment.NewBase = MovementBase;
4018 ServerData->PendingAdjustment.NewBaseBoneName = CharacterOwner->GetBasedMovement().BoneName;
4019 //ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(UpdatedComponent->GetComponentLocation(), this);
4020 ServerData->PendingAdjustment.NewRot = UpdatedComponent->GetComponentRotation();
4021
4023 {
4024 FVector CapsuleLoc = BaseVRCharacterOwner->GetVRLocation();
4025 CapsuleLoc.Z = UpdatedComponent->GetComponentLocation().Z;
4026 ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(CapsuleLoc, this);
4027 //ServerData->PendingAdjustment.NewRot = BaseVRCharacterOwner->GetVRRotation();
4028 }
4029 else
4030 {
4031 ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(UpdatedComponent->GetComponentLocation(), this);
4032 //ServerData->PendingAdjustment.NewRot = UpdatedComponent->GetComponentRotation();
4033 }
4034
4035 ServerData->PendingAdjustment.bBaseRelativePosition = MovementBaseUtility::UseRelativeLocation(MovementBase);
4036 if (ServerData->PendingAdjustment.bBaseRelativePosition)
4037 {
4038 // Relative location
4040 {
4041 FBasedMovementInfo BaseInfo = CharacterOwner->GetBasedMovement();
4042 FVector BaseLocation;
4043 FQuat BaseQuat;
4044 if (!MovementBaseUtility::GetMovementBaseTransform(BaseInfo.MovementBase, BaseInfo.BoneName, BaseLocation, BaseQuat))
4045 {
4046 ServerData->PendingAdjustment.NewLoc = CharacterOwner->GetBasedMovement().Location;
4047 }
4048 else
4049 {
4050 ServerData->PendingAdjustment.NewLoc = FTransform(BaseQuat, BaseLocation, FVector(1.0f)).InverseTransformPosition(ServerData->PendingAdjustment.NewLoc);
4051 }
4052 }
4053 else
4054 {
4055 ServerData->PendingAdjustment.NewLoc = CharacterOwner->GetBasedMovement().Location;
4056 }
4057
4058 // TODO: this could be a relative rotation, but all client corrections ignore rotation right now except the root motion one, which would need to be updated.
4059 //ServerData->PendingAdjustment.NewRot = CharacterOwner->GetBasedMovement().Rotation;
4060 }
4061
4062#if !UE_BUILD_SHIPPING
4063 static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections"));
4064 static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime"));
4065 if (CVarNetShowCorrections->GetInt() != 0)
4066 {
4067 const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc;
4068 const FString BaseString = MovementBase ? MovementBase->GetPathName(MovementBase->GetOutermost()) : TEXT("None");
4069 UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: Error for %s at Time=%.3f is %3.3f LocDiff(%s) ClientLoc(%s) ServerLoc(%s) Base: %s Bone: %s Accel(%s) Velocity(%s)"),
4070 *GetNameSafe(CharacterOwner), ClientTimeStamp, LocDiff.Size(), *LocDiff.ToString(), *ClientLoc.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *BaseString, *ServerData->PendingAdjustment.NewBaseBoneName.ToString(), *Accel.ToString(), *Velocity.ToString());
4071 const float DebugLifetime = CVarNetCorrectionLifetime->GetFloat();
4072 DrawDebugCapsule(GetWorld(), UpdatedComponent->GetComponentLocation(), CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(100, 255, 100), false, DebugLifetime);
4073 DrawDebugCapsule(GetWorld(), ClientLoc, CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(255, 100, 100), false, DebugLifetime);
4074 }
4075#endif
4076
4077 ServerData->LastUpdateTime = GetWorld()->TimeSeconds;
4078 ServerData->PendingAdjustment.DeltaTime = DeltaTime;
4079 ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp;
4080 ServerData->PendingAdjustment.bAckGoodMove = false;
4081 ServerData->PendingAdjustment.MovementMode = PackNetworkMovementMode();
4082
4083 //PerfCountersIncrement(PerfCounter_NumServerMoveCorrections);
4084 }
4085 else
4086 {
4087 if (bInClientAuthoritativeMovementMode || ServerShouldUseAuthoritativePosition(ClientTimeStamp, DeltaTime, Accel, ClientLoc, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode))
4088 {
4089 const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc; //-V595
4090 if (!LocDiff.IsZero() || ClientMovementMode != PackNetworkMovementMode() || GetMovementBase() != ClientMovementBase || (CharacterOwner && CharacterOwner->GetBasedMovement().BoneName != ClientBaseBoneName))
4091 {
4092 // Just set the position. On subsequent moves we will resolve initially overlapping conditions.
4093 UpdatedComponent->SetWorldLocation(ClientLoc, false); //-V595
4094
4095 // Trust the client's movement mode.
4096 ApplyNetworkMovementMode(ClientMovementMode);
4097
4098 // Update base and floor at new location.
4099 SetBase(ClientMovementBase, ClientBaseBoneName);
4100 UpdateFloorFromAdjustment();
4101
4102 // Even if base has not changed, we need to recompute the relative offsets (since we've moved).
4103 SaveBaseLocation();
4104
4105 LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector;
4106 LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity;
4107 LastUpdateVelocity = Velocity;
4108 }
4109 }
4110
4111 // acknowledge receipt of this successful servermove()
4112 ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp;
4113 ServerData->PendingAdjustment.bAckGoodMove = true;
4114 }
4115
4116 //PerfCountersIncrement(PerfCounter_NumServerMoves);
4117
4118 ServerData->bForceClientUpdate = false;
4119}
4120
4122{
4123 // This checks for a walking collision override on the penetrated object
4124 // If found then it stops penetration adjustments.
4125 if (MovementMode == EMovementMode::MOVE_Walking && VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && Hit.Component.IsValid())
4126 {
4127 ECollisionResponse WalkingResponse;
4128 WalkingResponse = Hit.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride);
4129
4130 if (WalkingResponse == ECR_Ignore || WalkingResponse == ECR_Overlap)
4131 {
4132 return FVector::ZeroVector;
4133 }
4134 }
4135
4136 FVector Result = Super::GetPenetrationAdjustment(Hit);
4137
4138 if (CharacterOwner)
4139 {
4140 const bool bIsProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy);
4141 float MaxDistance = bIsProxy ? MaxDepenetrationWithGeometryAsProxy : MaxDepenetrationWithGeometry;
4142 const AActor* HitActor = Hit.GetActor();
4143 if (Cast<APawn>(HitActor))
4144 {
4145 MaxDistance = bIsProxy ? MaxDepenetrationWithPawnAsProxy : MaxDepenetrationWithPawn;
4146 }
4147
4148 Result = Result.GetClampedToMaxSize(MaxDistance);
4149 }
4150
4151 return Result;
4152}
@ VRMOVEACTION_PauseTracking
#define devCodeVR(...)
DECLARE_CYCLE_STAT(TEXT("Char StepUp"), STAT_CharStepUp, STATGROUP_Character)
const float VERTICAL_SLOPE_NORMAL_Z
DEFINE_LOG_CATEGORY(LogVRCharacterMovement)
const float SWIMBOBSPEED
const float MAX_STEP_SIDE_Z
FVector SetActorLocationVR(FVector NewLoc, bool bTeleport)
UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations")
FVector GetVRLocation() const
UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations")
bool VRReplicateCapsuleHeight
UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRBaseCharacter")
virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps=true)
UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter")
UCLASS()
Definition VRCharacter.h:23
virtual void PrepMoveFor(ACharacter *Character) override
virtual void SetInitialPosition(ACharacter *C)
virtual void SetInitialPosition(ACharacter *C)
virtual void PrepMoveFor(ACharacter *Character) override
float VRClimbingEdgeRejectDistance
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing")
virtual bool VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character &ServerData) override
virtual void PerformMovement(float DeltaSeconds) override
virtual void PhysCustom(float deltaTime, int32 Iterations) override
virtual void OnClientCorrectionReceived(class FNetworkPredictionData_Client_Character &ClientData, float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent *NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode) override
virtual float SlideAlongSurface(const FVector &Delta, float Time, const FVector &Normal, FHitResult &Hit, bool bHandleImpact) override
virtual void ComputeFloorDist(const FVector &CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult &OutFloorResult, float SweepRadius, const FHitResult *DownwardSweepResult=NULL) const override
virtual void MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector &NewAccel) override
AVRBaseCharacter * BaseVRCharacterOwner
UPROPERTY(Transient, DuplicateTransient)
bool bUseClientControlRotation
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent")
bool bDisableSimulatedTickWhenSmoothingMovement
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent|Smoothing")
float VREdgeRejectDistance
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement")
float TrackingLossThreshold
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement", meta = (ClampMin = "0....
virtual void ApplyNetworkMovementMode(const uint8 ReceivedMode) override
virtual bool ShouldCheckForValidLandingSpot(float DeltaTime, const FVector &Delta, const FHitResult &Hit) const override
virtual void PhysFalling(float deltaTime, int32 Iterations) override
virtual bool CheckWaterJump(FVector CheckPoint, FVector &WallNormal) override
virtual void PhysSwimming(float deltaTime, int32 Iterations) override
virtual void FindFloor(const FVector &CapsuleLocation, FFindFloorResult &OutFloorResult, bool bCanUseCachedLocation, const FHitResult *DownwardSweepResult=NULL) const
float SwimVR(FVector Delta, FHitResult &Hit)
virtual void Crouch(bool bClientSimulation=false) override
virtual void ClientHandleMoveResponse(const FCharacterMoveResponseDataContainer &MoveResponse) override
virtual void ApplyRepulsionForce(float DeltaSeconds) override
virtual void UpdateBasedMovement(float DeltaSeconds) override
virtual void UnCrouch(bool bClientSimulation=false) override
UVRCharacterMovementComponent(const FObjectInitializer &ObjectInitializer=FObjectInitializer::Get())
FNetworkPredictionData_Server * GetPredictionData_Server() const override
virtual bool ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector &Accel, const FVector &ClientWorldLocation, float ClientYaw, const FVector &RelativeClientLocation, UPrimitiveComponent *ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode)
virtual void PhysNavWalking(float deltaTime, int32 Iterations) override
virtual bool VRClimbStepUp(const FVector &GravDir, const FVector &Delta, const FHitResult &InHit, FStepDownResult *OutStepDownResult=nullptr) override
void MoveSmooth(const FVector &InVelocity, const float DeltaSeconds, FStepDownResult *OutStepDownResult) override
bool bAllowMovementMerging
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VRCharacterMovementComponent")
virtual void PhysWalking(float deltaTime, int32 Iterations) override
FNetworkPredictionData_Client * GetPredictionData_Client() const override
virtual void MoveAlongFloor(const FVector &InVelocity, float DeltaSeconds, FStepDownResult *OutStepDownResult) override
UVRRootComponent * VRRootCapsule
UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement)
virtual FBasedPosition GetActorFeetLocationBased() const override
void StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations)
virtual FVector GetPenetrationAdjustment(const FHitResult &Hit) const override
void SimulateMovement(float DeltaSeconds) override
virtual bool IsWithinEdgeTolerance(const FVector &CapsuleLocation, const FVector &TestImpactPoint, const float CapsuleRadius) const override
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
virtual void CapsuleTouched(UPrimitiveComponent *OverlappedComp, AActor *Other, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult) override
void PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction &ThisTickFunction) override
virtual void ProcessLanded(const FHitResult &Hit, float remainingTime, int32 Iterations) override
virtual bool IsWithinClimbingEdgeTolerance(const FVector &CapsuleLocation, const FVector &TestImpactPoint, const float CapsuleRadius) const
virtual void ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector &Accel, const FVector &RelativeClientLocation, float ClientYaw, UPrimitiveComponent *ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode)
virtual void ServerMove_PerformMovement(const FCharacterNetworkMoveData &MoveData) override
virtual void ClientAdjustPositionVR_Implementation(float TimeStamp, FVector NewLoc, uint16 NewYaw, FVector NewVel, UPrimitiveComponent *NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode)
virtual void SetUpdatedComponent(USceneComponent *NewUpdatedComponent) override
bool bRunClientCorrectionToHMD
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VRCharacterMovementComponent")
bool StepUp(const FVector &GravDir, const FVector &Delta, const FHitResult &InHit, FStepDownResult *OutStepDownResult=NULL) override
bool SafeMoveUpdatedComponent(const FVector &Delta, const FQuat &NewRotation, bool bSweep, FHitResult &OutHit, ETeleportType Teleport=ETeleportType::None)
virtual void PhysFlying(float deltaTime, int32 Iterations) override
virtual FVector GetImpartedMovementBaseVelocity() const override
virtual void StoreSetTrackingPaused(bool bNewTrackingPaused) override
virtual void ReplicateMoveToServer(float DeltaTime, const FVector &NewAcceleration) override
static FRotator GetHMDPureYaw_I(FRotator HMDRotation)
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary)
FRotator StoredCameraRotOffset
FORCEINLINE void GenerateOffsetToWorld(bool bUpdateBounds=true, bool bGetPureYaw=true)
FVector DifferenceFromLastFrame
virtual void SetCapsuleSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps=true)
UFUNCTION(BlueprintCallable, Category = "Components|Capsule")
FTransform OffsetComponentToWorld
bool bUseWalkingCollisionOverride
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary")
TEnumAsByte< ECollisionChannel > WalkingCollisionOverride
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary")
FAutoConsoleVariableRef CVarRotationCorrectionThreshold(TEXT("vre.RotationCorrectionThreshold"), fRotationCorrectionThreshold, TEXT("Rotation is replicated at 2 decimal precision, so values less than 0.01 won't matter."), ECVF_Default)
EVRConjoinedMovementModes ReplicatedMovementMode
FVector RequestedVelocity
UPROPERTY(Transient)
FVector CustomVRInputVector
UPROPERTY(Transient)
FVRMoveActionArray MoveActionArray
UPROPERTY(Transient)
TArray< FVRMoveActionContainer > MoveActions
UPROPERTY()
EVRMoveAction MoveAction
UPROPERTY()