Rare Collision Notify Crash

Hello,

There seems to be a bug in the physics code which will occasionally crash the engine when a collision notify occurs. I have a hard time to reproduce the issue, since it appears in connection with my destruction physics code which is quite complex. However I can say that it happens after a welded body was detached from a physics simulating body and then set to simulate physics as well.

This is what happens in BodyInstance.cpp:

TArray<int32> FBodyInstance::AddCollisionNotifyInfo(const FBodyInstance* Body0, const FBodyInstance* Body1, const physx::PxContactPair * Pairs, uint32 NumPairs, TArray<FCollisionNotifyInfo> & PendingNotifyInfos)
{
	TArray<int32> PairNotifyMapping;
	PairNotifyMapping.Empty(NumPairs);

	TMap<const FBodyInstance *, TMap<const FBodyInstance *, int32> > BodyPairNotifyMap;
	for (uint32 PairIdx = 0; PairIdx < NumPairs; ++PairIdx)
	{
		const PxContactPair* Pair = Pairs + PairIdx;
		// Get the two shapes that are involved in the collision
		const PxShape* Shape0 = Pair->shapes[0];
		check(Shape0);
		const PxShape* Shape1 = Pair->shapes[1];
		check(Shape1);

		PairNotifyMapping.Add(-1);	//start as -1 because we can have collisions that we don't want to actually record collision

		const FBodyInstance* SubBody0 = Body0->GetOriginalBodyInstance(Shape0);
		const FBodyInstance* SubBody1 = Body1->GetOriginalBodyInstance(Shape1);
		
		PxU32 FilterFlags0 = Shape0->getSimulationFilterData().word3 & 0xFFFFFF;
		PxU32 FilterFlags1 = Shape1->getSimulationFilterData().word3 & 0xFFFFFF;

The game crashes on the “Shape0->getSimulationFilterData().word3 & 0xFFFFFF” line, because the Shape0 variable is not valid. A workaround for this would be highly appreciated (I can modify the source), it does crash the game a lot and disrupts the gameplay.

Yep so I found the solution. Apparently during a collision a shape can be removed from the scene, and in that case is not valid anymore but still present to keep the data. In order to prevent a crash in that situation the function has to check if one of the shapes was deleted, which is stored in the flags property of the Pair variable.
Here is what the fixed function now looks like:

TArray<int32> FBodyInstance::AddCollisionNotifyInfo(const FBodyInstance* Body0, const FBodyInstance* Body1, const physx::PxContactPair * Pairs, uint32 NumPairs, TArray<FCollisionNotifyInfo> & PendingNotifyInfos)
{
	TArray<int32> PairNotifyMapping;
	PairNotifyMapping.Empty(NumPairs);

	TMap<const FBodyInstance *, TMap<const FBodyInstance *, int32> > BodyPairNotifyMap;
	for (uint32 PairIdx = 0; PairIdx < NumPairs; ++PairIdx)
	{
		const PxContactPair* Pair = Pairs + PairIdx;

		PairNotifyMapping.Add(-1);	//start as -1 because we can have collisions that we don't want to actually record collision

		if (!Pair->flags.isSet(PxContactPairFlag::eREMOVED_SHAPE_0) && !Pair->flags.isSet(PxContactPairFlag::eREMOVED_SHAPE_1))
		{
			// Get the two shapes that are involved in the collision
			const PxShape* Shape0 = Pair->shapes[0];
			check(Shape0);
			const PxShape* Shape1 = Pair->shapes[1];
			check(Shape1);

			const FBodyInstance* SubBody0 = Body0->GetOriginalBodyInstance(Shape0);
			const FBodyInstance* SubBody1 = Body1->GetOriginalBodyInstance(Shape1);

			PxU32 FilterFlags0 = Shape0->getSimulationFilterData().word3 & 0xFFFFFF;
			PxU32 FilterFlags1 = Shape1->getSimulationFilterData().word3 & 0xFFFFFF;

			const bool bBody0Notify = (FilterFlags0&EPDF_ContactNotify) != 0;
			const bool bBody1Notify = (FilterFlags1&EPDF_ContactNotify) != 0;

			//const bool bBody0Notify = Body0->bNotifyRigidBodyCollision;
			//const bool bBody1Notify = Body1->bNotifyRigidBodyCollision;

			if (bBody0Notify || bBody1Notify)
			{
				TMap<const FBodyInstance *, int32> & SubBodyNotifyMap = BodyPairNotifyMap.FindOrAdd(SubBody0);
				int32* NotifyInfoIndex = SubBodyNotifyMap.Find(SubBody1);

				if (NotifyInfoIndex == NULL)
				{
					FCollisionNotifyInfo * NotifyInfo = new (PendingNotifyInfos) FCollisionNotifyInfo;
					NotifyInfo->bCallEvent0 = (bBody0Notify);
					NotifyInfo->Info0.SetFrom(SubBody0);
					NotifyInfo->bCallEvent1 = (bBody1Notify);
					NotifyInfo->Info1.SetFrom(SubBody1);

					NotifyInfoIndex = &SubBodyNotifyMap.Add(SubBody0, PendingNotifyInfos.Num() - 1);
				}

				PairNotifyMapping[PairIdx] = *NotifyInfoIndex;
			}
		}
	}

	return PairNotifyMapping;
}

Hope Epic acknowledges this for a future engine udpdate, if not I hope this helps others who run into the same annoying problem.