Adjust collision penetration, but only along a given axis?

Not sure if anybody has tried this and is able to help. I’m trying to get a ‘Safe’ aim location for my games’ abilities. The pawns are just spheres and the game is top-down (but 3D), which makes life a little easier.

In order to get the desired ‘Aim Location’, I simply get the stick magnitude, multiply it by the abilities ‘range’, then do a trace down from the sky to find the floor at that location. I then offset that location by the height of the orb from it’s floor, and that’s my ‘Target Location’ Quite easy right?



    const FVector ShootDirection = FRotator(0.f, ShootYaw, 0.f).Vector();
    const FVector2D AimDirXY = FVector2D(ShootDirection);
    const FVector2D OrbPosXY = FVector2D(OrbLocation);

    // Clamp to maximum possible based on ammo
    // We have to reduce maximum a tiny bit, to account for floating point error when checking if we can fire or not.
    const float MaxPossibleDistance = GetMaxPossibleTravelDistance();
    const float ClampMax = FMath::Max(MaxPossibleDistance, AimableConfig.MinRange) - MAX_DISTANCE_FUDGE_FACTOR;
    const float AimTravelDistance = FMath::Min(FMath::GetMappedRangeValueClamped(FVector2D(0.f, 1.f), FVector2D(AimableConfig.MinRange, AimableConfig.MaxRange), InAimMagnitude), ClampMax);
    FVector2D AimEndPointXY = OrbPosXY + (AimDirXY * AimTravelDistance);

    const FVector TraceStart = FVector(AimEndPointXY, WORLD_MAX);
    const FVector TraceEnd = FVector(AimEndPointXY, MyWS->KillZ);


Now here’s the tricky part - I need that resulting location to be ‘Made Safe’, so that the Sphere can be placed there without being blocked by surrounding geometry. This is how I do it right now, by getting the overlapping collisions at that location, and applying the required penetration adjustment. This is similar to how Unreal does it for actors and components.



bool UECGameplayStatics::TryAdjustment(UWorld* InWorldContext, const FCollisionShape& InCollisionShape, const FVector& InTestLocation, FVector& OutSafeLocation, const ECollisionChannel InBlockingChannel, const FCollisionQueryParams& InQParams, const FCollisionResponseParams& InRParams)
{
    // Compute Adjustment, if we found a blocking hit
    TArray<FOverlapResult> Overlaps;
    const bool bFoundBlockingHit = InWorldContext->OverlapMultiByChannel(Overlaps, InTestLocation, FQuat::Identity, InBlockingChannel, InCollisionShape, InQParams, InRParams);
    if (bFoundBlockingHit)
    {
        FVector ProposedAdjustment = FVector::ZeroVector;
        FMTDResult MTDResult;
        uint32 NumBlockingHits = 0;
        for (const FOverlapResult& OverlapItr : Overlaps)
        {
            UPrimitiveComponent* const OverlapComponent = OverlapItr.GetComponent();
            if (OverlapComponent && OverlapComponent->GetCollisionResponseToChannel(InBlockingChannel) == ECR_Block)
            {
                NumBlockingHits++;
                const bool bSuccess = OverlapComponent->ComputePenetration(MTDResult, InCollisionShape, InTestLocation, FQuat::Identity);
                if (bSuccess)
                {
                    ProposedAdjustment += MTDResult.Direction * MTDResult.Distance;
                }
                else
                {
                    UE_LOG(LogECGame, Warning, TEXT("TryAdjustment: Invalid adjusted collision for %s (%s). Dist: %f"), *GetNameSafe(OverlapComponent), *GetNameSafe(OverlapComponent->GetOwner()), MTDResult.Distance);
                    return false;
                }
            }
        }

        if (NumBlockingHits == 0 || ProposedAdjustment.IsZero())
        {
            OutSafeLocation = InTestLocation;
            return true;
        }
        else
        {
            OutSafeLocation = InTestLocation + ProposedAdjustment;
            return true;
        }
    }
    else
    {
        OutSafeLocation = InTestLocation;
        return true;
    }
}


Again - pretty simple right? The problem is, I ONLY want to adjust collision along the axis that I’m aiming along, because I don’t want the resulting aim position moving too much and I don’t want the position to exceed the range. I also don’t want it being propelled up into the Sky etc.

So, given the code above, if I know my ‘Aim Vector’, how can I make this work so that the ProposedAdjustment only goes back along the Aim Vector? I can’t just do loads of sphere traces, because there’s an almost infinite number of possibilities and I need this code to be as fast as possible.

Any ideas?

Just as an afterthought, I can do this, but it doesn’t necessarily mean I won’t still be overlapping some objects… hmm.



                    // We can only adjusted collision BACK along the aim axis, unfortunately, this makes this an iterative approach :/
                    //ProposedAdjustment += MTDResult.Direction * MTDResult.Distance;
                    const float PenetrationDot = FVector::DotProduct(AdjustAxis, MTDResult.Direction);
                    if (PenetrationDot > 0.f)
                    {
                        ProposedAdjustment += -AdjustAxis * (MTDResult.Distance * PenetrationDot);
                    }


It sounds like you just want a version of “sweep” that only works with two geometric primitives. The PhysX sweep function (in PxGeometryQuery.h) may be what you need. Also see BodyInstance.cpp for how UE4 calls this function.

It may be possible to “sweep” a sphere outwards along the aim direction. I’m not 100% sure about this, but perhaps the result of the sweep (FHitResult) will tell you about where the sphere left the overlapping geometry.

Another option is just to pick a point (say 10,000 units away) along the aim direction, and then sweep inwards towards the overlapping geometry (using something like KismetSystemLimbrary::SphereTraceMulti). Look through the returned FHitResults until you find the one that describes the impact point between the sphere and the world geometry. This is probably not a very efficient solution, so you may need to optimize this on your own.

One more thing: once you find the penetration depth along the aim direction, you’ll need to make sure that your sphere is still sitting on the floor after it has been moved.