void UGripMotionControllerComponent::TickGrip() { if (GrippedActors.Num()) { FTransform WorldTransform; FTransform InverseTransform = this->GetComponentTransform().Inverse(); for (int i = GrippedActors.Num() - 1; i >= 0; --i) { if (GrippedActors[i].Actor || GrippedActors[i].Component) { // GetRelativeTransformReverse had some serious f*****g floating point errors associated with it that was f*****g everything up // Not sure whats wrong with the function but I might want to push a patch out eventually WorldTransform = GrippedActors[i].RelativeTransform.GetRelativeTransform(InverseTransform); UPrimitiveComponent *root = GrippedActors[i].Component; AActor *actor = GrippedActors[i].Actor; if (!root) root = Cast<UPrimitiveComponent>(GrippedActors[i].Actor->GetRootComponent()); if (!root) continue; if (!actor) actor = root->GetOwner(); if (!actor) continue; if (bIsServer) { // Handle collision intersection detection, this is used to intelligently manage some of the networking and late update features. switch (GrippedActors[i].GripCollisionType.GetValue()) { case EGripCollisionType::InteractiveCollisionWithPhysics: case EGripCollisionType::InteractiveHybridCollisionWithSweep: { TArray<FOverlapResult> Hits; FComponentQueryParams Params(NAME_None, this->GetOwner()); Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; Params.AddIgnoredActor(actor); Params.AddIgnoredActors(root->MoveIgnoreActors); if(GetWorld()->ComponentOverlapMulti(Hits, root, root->GetComponentLocation(), root->GetComponentQuat(), Params)) { GrippedActors[i].bColliding = true; } else { GrippedActors[i].bColliding = false; } } // Skip collision intersects with these types, they dont need it case EGripCollisionType::PhysicsOnly: case EGripCollisionType::SweepWithPhysics: case EGripCollisionType::InteractiveCollisionWithSweep: default:break; } } // Need to figure out best default behavior /*if (GrippedActors[i].bHasSecondaryAttachment && GrippedActors[i].SecondaryAttachment) { WorldTransform.SetRotation((WorldTransform.GetLocation() - GrippedActors[i].SecondaryAttachment->GetComponentLocation()).ToOrientationRotator().Quaternion()); }*/ if (GrippedActors[i].GripCollisionType == EGripCollisionType::InteractiveCollisionWithPhysics) { UpdatePhysicsHandleTransform(GrippedActors[i], WorldTransform); } else if (GrippedActors[i].GripCollisionType == EGripCollisionType::InteractiveCollisionWithSweep) { if (bIsServer) { FHitResult OutHit; // Need to use without teleport so that the physics velocity is updated for when the actor is released to throw root->SetWorldTransform(WorldTransform, true, &OutHit); if (OutHit.bBlockingHit) { GrippedActors[i].bColliding = true; if (!actor->bReplicateMovement) actor->SetReplicateMovement(true); } else { GrippedActors[i].bColliding = false; if (actor->bReplicateMovement) actor->SetReplicateMovement(false); } } else { if (!GrippedActors[i].bColliding) { root->SetWorldTransform(WorldTransform, true); } } } else if (GrippedActors[i].GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithSweep) { // Need to use without teleport so that the physics velocity is updated for when the actor is released to throw //if (bIsServer) //{ FBPActorPhysicsHandleInformation * GripHandle = GetPhysicsGrip(GrippedActors[i]); if (!GrippedActors[i].bColliding) { // Make sure that there is no collision on course before turning off collision and snapping to controller if (bIsServer && actor && actor->bReplicateMovement) { FCollisionQueryParams QueryParams(NAME_None, false, this->GetOwner()); FCollisionResponseParams ResponseParam; root->InitSweepCollisionParams(QueryParams, ResponseParam); QueryParams.AddIgnoredActor(actor); QueryParams.AddIgnoredActors(root->MoveIgnoreActors); if (GetWorld()->SweepTestByChannel(root->GetComponentLocation(), WorldTransform.GetLocation(), root->GetComponentQuat(), ECollisionChannel::ECC_Visibility, root->GetCollisionShape(), QueryParams, ResponseParam)) { GrippedActors[i].bColliding = true; } else { GrippedActors[i].bColliding = false; } } // If still not colliding if (!GrippedActors[i].bColliding) { if (bIsServer && actor->bReplicateMovement) // So we don't call on on change event over and over locally actor->SetReplicateMovement(false); if (bIsServer && GripHandle) { DestroyPhysicsHandle(GrippedActors[i]); if(GrippedActors[i].Actor) GrippedActors[i].Actor->DisableComponentsSimulatePhysics(); else root->SetSimulatePhysics(false); } root->SetWorldTransform(WorldTransform, false); } else { if (bIsServer && GrippedActors[i].bColliding && GripHandle) UpdatePhysicsHandleTransform(GrippedActors[i], WorldTransform); } } else if (GrippedActors[i].bColliding && !GripHandle) { if (bIsServer && !actor->bReplicateMovement) actor->SetReplicateMovement(true); root->SetSimulatePhysics(true); if (bIsServer) SetUpPhysicsHandle(GrippedActors[i]); } else { if (bIsServer && GrippedActors[i].bColliding && GripHandle) UpdatePhysicsHandleTransform(GrippedActors[i], WorldTransform); } } else if (GrippedActors[i].GripCollisionType == EGripCollisionType::SweepWithPhysics) { if (bIsServer) { FVector OriginalPosition(root->GetComponentLocation()); FRotator OriginalOrientation(root->GetComponentRotation()); FVector NewPosition(WorldTransform.GetTranslation()); FRotator NewOrientation(WorldTransform.GetRotation()); // Now sweep collision separately so we can get hits but not have the location altered if (bUseWithoutTracking || NewPosition != OriginalPosition || NewOrientation != OriginalOrientation) { FVector move = NewPosition - OriginalPosition; // ComponentSweepMulti does nothing if moving < KINDA_SMALL_NUMBER in distance, so it's important to not try to sweep distances smaller than that. const float MinMovementDistSq = (FMath::Square(4.f*KINDA_SMALL_NUMBER)); if (bUseWithoutTracking || move.SizeSquared() > MinMovementDistSq || NewOrientation != OriginalOrientation) { if (CheckComponentWithSweep(root, move, NewOrientation, false)) { } } } // Move the actor, we are not offsetting by the hit result anyway root->SetWorldTransform(WorldTransform, false); } else { // Move the actor, we are not offsetting by the hit result anyway root->SetWorldTransform(WorldTransform, false); } } else if (GrippedActors[i].GripCollisionType == EGripCollisionType::PhysicsOnly) { // Move the actor, we are not offsetting by the hit result anyway root->SetWorldTransform(WorldTransform, false); } } else { if (bIsServer) { DestroyPhysicsHandle(GrippedActors[i]); GrippedActors.RemoveAt(i); // If it got garbage collected then just remove the pointer, won't happen with new uproperty use, but keeping it here anyway } } } } }
bool UGripMotionControllerComponent::CheckComponentWithSweep(UPrimitiveComponent * ComponentToCheck, FVector Move, FRotator newOrientation, bool bSkipSimulatingComponents/*, bool &bHadBlockingHitOut*/) { TArray<FHitResult> Hits; // WARNING: HitResult is only partially initialized in some paths. All data is valid only if bFilledHitResult is true. FHitResult BlockingHit(NoInit); BlockingHit.bBlockingHit = false; BlockingHit.Time = 1.f; bool bFilledHitResult = false; bool bMoved = false; bool bIncludesOverlapsAtEnd = false; bool bRotationOnly = false; UPrimitiveComponent *root = ComponentToCheck; if (!root || !root->IsQueryCollisionEnabled()) return false; FVector start(root->GetComponentLocation()); const bool bCollisionEnabled = root->IsQueryCollisionEnabled(); if (bCollisionEnabled) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (!root->IsRegistered()) { UE_LOG(LogTemp, Warning, TEXT("MovedComponent %s not initialized in grip motion controller"), *root->GetFullName()); } #endif UWorld* const MyWorld = GetWorld(); FComponentQueryParams Params(TEXT("sweep_params"), root->GetOwner()); FCollisionResponseParams ResponseParam; root->InitSweepCollisionParams(Params, ResponseParam); bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, root, start, start + Move, newOrientation, Params); if (bHadBlockingHit) { int32 BlockingHitIndex = INDEX_NONE; float BlockingHitNormalDotDelta = BIG_NUMBER; for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) { const FHitResult& TestHit = Hits[HitIdx]; // Ignore the owning actor to the motion controller if (TestHit.Actor == this->GetOwner() || (bSkipSimulatingComponents && TestHit.Component->IsSimulatingPhysics())) { if (Hits.Num() == 1) { //bHadBlockingHitOut = false; return false; } else continue; } if (TestHit.bBlockingHit && TestHit.IsValidBlockingHit()) { if (TestHit.Time == 0.f) { // We may have multiple initial hits, and want to choose the one with the normal most opposed to our movement. const float NormalDotDelta = (TestHit.ImpactNormal | Move); if (NormalDotDelta < BlockingHitNormalDotDelta) { BlockingHitNormalDotDelta = NormalDotDelta; BlockingHitIndex = HitIdx; } } else if (BlockingHitIndex == INDEX_NONE) { // First non-overlapping blocking hit should be used, if an overlapping hit was not. // This should be the only non-overlapping blocking hit, and last in the results. BlockingHitIndex = HitIdx; break; } //} } } // Update blocking hit, if there was a valid one. if (BlockingHitIndex >= 0) { BlockingHit = Hits[BlockingHitIndex]; bFilledHitResult = true; } } } // Handle blocking hit notifications. Avoid if pending kill (which could happen after overlaps). if (BlockingHit.bBlockingHit && !root->IsPendingKill()) { check(bFilledHitResult); if (root->IsDeferringMovementUpdates()) { FScopedMovementUpdate* ScopedUpdate = root->GetCurrentScopedMovement(); ScopedUpdate->AppendBlockingHitAfterMove(BlockingHit); } else { if(root->GetOwner()) root->DispatchBlockingHit(*root->GetOwner(), BlockingHit); } return true; } return false; }