Hitting asteroids: avoid to handle same hit twice (once by each asteroid)

I have asteroid that hit each other and then bounce off each other. This works just fine in about 90% of the time. Only sometimes, two asteroids don’t bounce off but rather seem to be glued together when they hit, until they disentangle eventually and continue on their respective paths. Those paths are wrong, though: when they don’t bounce off immediately, the whole hit calculation is off.

This glue-incident happens quite randomly. It can happen right at the first hit, when I simulate or I can have the simulation run for minutes without glue-incidents. It isn’t restricted to specific asteroids, either.

Based on log output, I have a suspicion of what might be the cause.

This is my code that moves (sweeps!) asteroids around and triggers the handling of a blocking hit, if there is one.

    if(RP_Body->Implements<UHasMesh>() && RP_Body->Implements<UHasCollision>())
    {
        FHitResult HitResult;
        auto* PrimitiveComponent = Cast<IHasMesh>(RP_Body)->GetMesh();
        PrimitiveComponent->SetWorldLocation(NewVecR, true, &HitResult);
        if(HitResult.bBlockingHit && HitResult.GetActor()->Implements<UHasCollision>())
        {
            Cast<IHasCollision>(RP_Body)->GetCollisionComponent()->HandleHit(HitResult);
        }
        else
        {
            RP_Body->SetActorLocation(NewVecR);
        }
        PrimitiveComponent->SetRelativeLocation(FVector::Zero());
    }
    else
    {
        RP_Body->SetActorLocation(NewVecR);
    }

// ...

void UMyCollisionComponent::HandleHit(FHitResult& HitResult)
{
	auto* Other = HitResult.GetActor();
	
	// Normal is based on the object that was swept, 'ImpactNormal' is based on the object that was hit
	// Still, both are the same unless the object that was hit has a collision shape that isn't sphere or plane
	// In case two non-capsule shapes collide, I don't know what the value of 'Normal' would be

	UE_LOG(LogMyGame, Warning, TEXT("%s: Blocking hit with %s")
		, *GetFullName()
		, *Other->GetName()
		)
	
	auto* Orbit1 = GetOwner<IHasOrbit>()->GetOrbit();
	auto* Orbit2 = Cast<IHasOrbit>(Other)->GetOrbit();
	const FVector VecV1 = Orbit1->GetVecVelocity();
	const FVector VecV2 = Orbit2->GetVecVelocity();
	// TODO: use PhysicsMaterial for mass density
	const double M1 = GetOwner<IHasMesh>()->GetMyMass();
	const double M2 = Cast<IHasMesh>(HitResult.GetActor())->GetMyMass();
	// the idea is to subtract the CoM velocity to make sure that u1 and u2 are on plane with the normal,
	// I am not sure about that, however
	// new base vectors: VecN, VecO
	const FVector VecN = HitResult.ImpactNormal;
	// U1 = Alpha1 * VecN + Beta1 * VecO
	const double Alpha1 = VecV1.Dot(VecN);
	const FVector VecU1O = VecV1 - Alpha1 * VecN;
	const double Alpha2 = VecV2.Dot(VecN);
	const FVector VecU2O = VecV2 - Alpha2 * VecN;

	const double UBar = (M1 * Alpha1 + M2 * Alpha2) / (M1 + M2);
	// TODO: use PhysicsMaterial for k value, e.g. k = std::min(1., k1 + k2)
	//double K = 0.5;
	double K = 1.;
	// partially elastic collision, k in [0, 1] where k = 0 is plastic and k = 1 elastic collision, respectively
	const FVector VecW1 = (UBar - M2 * (Alpha1 - Alpha2) / (M1 + M2) * K) * VecN + VecU1O;
	const FVector VecW2 = (UBar - M1 * (Alpha2 - Alpha1) / (M1 + M2) * K) * VecN + VecU2O;

	auto* MyState = GEngine->GetEngineSubsystem<UMyState>();
	const auto* GI = GetWorld()->GetGameInstance<UMyGameInstance>();
	const auto* GS = GetWorld()->GetGameState<AMyGameState>();
	const FPhysics Physics = MyState->GetPhysics(GS);
	const FInstanceUI InstanceUI = MyState->GetInstanceUI(GI);
	Orbit1->Update(VecW1 - VecV1, Physics, InstanceUI);
	Orbit2->Update(VecW2 - VecV2, Physics, InstanceUI);
}

When asteroids glue-hit (instead of bouncing off as intended), I get log messages indicating that asteroid 1 hits asteroid 2 … and then asteroid 2 hits asteroid 1.

That is: When the hit works fine, I get one hit only. Asteroid 1 hits asteroid 2, deals correctly with the hit for both asteroids (Orbit1->Update(...); Orbit2->Update(…);`), and everything is fine.

So maybe, the weird glue-hitting is caused by a second erroneous hit: when asteroid 2 wants to deal with the hit, the orbits (and thus the velocities) have been updated already and the result of this second hit is necessarily nonsensical.

Now the question:

How to make sure that I handle every hit once? This isn’t trivial (I think), because it is perfectly fine that two asteroids hit each other in short succession a couple of times. I have to identify hits exactly and only filter out actual duplicates.


I thought about an arbitrary ranking in my asteroids (e.g. by name) and then I only deal with asteroid1 hitting asteroid2, or in general AsteroidN hitting AsteroidM for N < M.

I might end up with hits that start in penetration, however, which would complicate things further.