Page 1 of 1

Bepu v2 Collision Detection

Posted: Wed Sep 11, 2019 4:02 pm
by tomweiland
I have a sphere collider acting as a cannonball and I want it to explode whenever it hits something. I don't need to know the force of the impact, just when and where it occurred in the simulation so I can get particles/sounds going. I also need to know if the object it collided with was a ship (which is a compound body).

I'm pretty sure the ContactEventsDemo will allow me to do most of this—is it the best way to go about it, though?

Re: Bepu v2 Collision Detection

Posted: Wed Sep 11, 2019 8:17 pm
by Norbo
Something like the ContactEventsDemo could work, although it is a bit fatter than you might need. As luck would have it, I just finished a TankDemo in which a bunch of tanks try to blow each other up. It shows a pretty simple/direct way of reporting projectile impacts and processing them: https://github.com/bepu/bepuphysics2/bl ... llbacks.cs

Re: Bepu v2 Collision Detection

Posted: Mon Nov 11, 2019 8:29 pm
by tomweiland
Alright so I meant to get this stuff sorted out a long time ago, but ended up getting sidetracked by various other things (like rewinding the simulation).

I'm just coming back to it, and as I'm reading through some of the comments in the NarrowPhaseCallback functions, it sounds like collision handling is quite different when dealing with compound bodies.

My ship is a compound body made of a little over 100 primitives, and I need to be able to detect when (and where on the ship) a cannonball—which is a simple sphere collider—hits it.

Based on these comments I'm not sure that that the TankDemo covers what I need...do I need to take different or additional steps in the case of dealing with compounds?

Re: Bepu v2 Collision Detection

Posted: Mon Nov 11, 2019 9:57 pm
by Norbo
Handling compounds in callbacks is nearly the same, just two differences:
1) If an incoming convex hits more than one convex in the compound at the same time, ConfigureContactManifold will be called with a NonconvexContactManifold rather than a ConvexContactManifold. NonconvexContactManifolds have per-contact normals, ConvexContactManifolds share one normal for all contacts.
2) Compounds also trigger callbacks for each child collision (the ones that accept child indices). These occur before the final top-level ConfigureContactManifold and give you the opportunity to filter or make modifications to the contact before it's reduced into the final manifold. If you don't need to do any per-child filtering/customization, you can just return true in those callbacks.

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 12:31 am
by tomweiland
So I'm just in the process of implementing the things I need from the tank demo, but I'm going to need to be able to tell which part of the ship was hit. I want cannonballs to explode anytime they hit anything, however the damage done to the ship needs to be different. For example, I only want cannonballs to leave holes when they hit a part of the hull, so if they hit the deck they should still explode, but shouldn't leave a hole. In the future I might make it possible to destroy the mast as well, but that would require a different collision event...I'm thinking something like 3 hits before it breaks.

Is there a way to assign body properties to a compound body's specific children (like the hull colliders, mast, deck, etc. all get a different property) and can I use this to somehow cause a different function to be called in my ship class?

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 12:45 am
by tomweiland
Also, I just noticed something which I'm wondering whether or not is a mistake.

On line 135 of the Tank Demo's TankCallbacks file, there's parentheses around the middle condition of the if statement:

Code: Select all

if (propertiesA.Projectile || (pair.B.Mobility != CollidableMobility.Static) && Properties[pair.B.Handle].Projectile)
Is this intentional? If not, I'm assuming they were meant to encompass the second and third conditions, like this:

Code: Select all

if (propertiesA.Projectile || (pair.B.Mobility != CollidableMobility.Static && Properties[pair.B.Handle].Projectile))
Unless of course they're just not meant to be there at all, since they currently have no effect on the logic...

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 2:43 am
by Norbo
Is there a way to assign body properties to a compound body's specific children (like the hull colliders, mast, deck, etc. all get a different property) and can I use this to somehow cause a different function to be called in my ship class?
There's not a demo showing it, but the callbacks which pass the child indices can be used to do this. For example, you could store a buffer for each body representing the metadata attached to each of the body's children, and index into that data using the childIndex. Based on that metadata you could choose to invoke different functions.

Same idea as the TankDemo, just with logic performed on a per-child basis.
On line 135 of the Tank Demo's TankCallbacks file, there's parentheses around the middle condition of the if statement:
If I had to guess, I probably meant to surround the && clause just to avoid having to think about operator precedence later. Given the precedence, it doesn't affect the logic either way, but I'll scoot it over.

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 5:38 am
by tomweiland
Norbo wrote: Fri Nov 15, 2019 2:43 am There's not a demo showing it, but the callbacks which pass the child indices can be used to do this. For example, you could store a buffer for each body representing the metadata attached to each of the body's children, and index into that data using the childIndex. Based on that metadata you could choose to invoke different functions.

Same idea as the TankDemo, just with logic performed on a per-child basis.
Alright, that makes sense.

In the TankCallbacks' TryAddProjectileImpact method, you add the impact's info to the ProjectileImpacts QuickList and then process it in the TankDemo's update method. I'm guessing this is necessary to keep the SpinLock from staying locked while you're making things explode (although I've never used SpinLocks so I don't really know how they work), or could I actually run my explosion logic directly inside the TryAddProjectileImpact method?

Out of curiosity, on line 89 of the TankCallbacks file, why do you use ++i instead of i++ in the for loop?

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 7:29 pm
by tomweiland
I've finally put everything together so it allows me to run it, however there's an issue.

Everything works fine until a cannonball hits a ship, at which point this line:

Code: Select all

ref CannonballImpact _newImpact = ref cannonballImpacts.AllocateUnsafely();
In the TryAddCannonballImpact method (which is the TryAddProjectileImpact method in the TankDemo) produces this error:
Unsafe adders can only be used if the capacity is guaranteed to hold the new size.

As far as I can tell, I've done everything identically to the TankDemo, aside from renaming a few things. What could be causing this?

Re: Bepu v2 Collision Detection

Posted: Fri Nov 15, 2019 10:02 pm
by Norbo
In the TankCallbacks' TryAddProjectileImpact method, you add the impact's info to the ProjectileImpacts QuickList and then process it in the TankDemo's update method. I'm guessing this is necessary to keep the SpinLock from staying locked while you're making things explode (although I've never used SpinLocks so I don't really know how they work), or could I actually run my explosion logic directly inside the TryAddProjectileImpact method?
SpinLocks do indeed need to be very brief to ensure decent performance, but the main concern was correctness. The deferred impact handling logic removes the projectile body and sometimes makes other modifications. Making modification to the simulation while it's in the process of updating (as it is during the narrow phase callbacks) would likely lead to corruption.
Out of curiosity, on line 89 of the TankCallbacks file, why do you use ++i instead of i++ in the for loop?
Eons and eons ago, compilers did not always optimize out the unused 'return current value' part of the postfix operator in a forloop. Since the prefix operator didn't store a separate value, it was more frequently handled optimally. That's pretty much irrelevant today- I haven't actually looked at the codegen on modern C# runtimes, but I'd be incredibly surprised if there was any difference. So in other words, it's just a habit. :)
As far as I can tell, I've done everything identically to the TankDemo, aside from renaming a few things. What could be causing this?
The TankDemo allocates a result slot for each projectile that exists on the current frame, then prevents more than one report from being added for each projectile.

It sounds like either the allocated size for the impact list wasn't large enough, or more than one result got added for a projectile.

Re: Bepu v2 Collision Detection

Posted: Mon Nov 18, 2019 11:00 pm
by tomweiland
I solved the problem and now cannonballs can collide with ships, but when I added the functionality I needed to actually inflict damage on said ship, something went very, very wrong.

As soon as a cannonball collides with a ship, Visual Studio opens a page that says "the application is in break mode". I have the following exception:
Managed Debugging Assistant 'FatalExecutionEngineError' : 'The runtime has encountered a fatal error. The address of the error was at 0x6cf8ed98, on thread 0x44e0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.'

It looks like it can't access the memory location of the Cannonball instance that I'm storing in the body's properties. My body properties struct looks like this:

Code: Select all

internal struct BodyProperties
{
    public SubgroupCollisionFilter filter;
    public float friction;
    /// <summary>Null if this is not a ship.</summary>
    public Ship ship;
    /// <summary>Null if this is not a cannonball.</summary>
    public Cannonball cannonball;
}
I was hoping to be able to store references to the classes that manage the logic for ships and cannonballs, since otherwise I don't really have a way of accessing them. Is storing references in a body property a bad idea? Is that what's causing this issue? Is there a way around this?

Not sure if this might help diagnose what's going wrong here, but my ConfigureContactManifold method is the same as the TankDemo one, except for the big if statement, that looks like this:

Code: Select all

if (propertiesA.cannonball != null || (pair.B.Mobility != CollidableMobility.Static && properties[pair.B.Handle].cannonball != null))
{
    for (int i = 0; i < manifold.Count; i++)
    {
        if (manifold.GetDepth(ref manifold, i) >= 0)
        {
            if (propertiesA.cannonball != null)
            {
                TryAddCannonballImpact(propertiesA.cannonball, pair.B);
            }
            if (pair.B.Mobility != CollidableMobility.Static && properties[pair.B.Handle].cannonball != null)
            {
                TryAddCannonballImpact(properties[pair.B.Handle].cannonball, pair.A);
            }
            break;
        }
    }
}
Accordingly, I've changed the TryAddProjectileImpact method's try block's contents to this:

Code: Select all

for (int i = 0; i < cannonballImpacts.Count; i++)
{
    ref CannonballImpact _impact = ref cannonballImpacts[i];
    if (_impact.cannonball == _cannonball)
    {
        // If the cannonball has already been handled, ignore it
        return;
    }
}

ref CannonballImpact _newImpact = ref cannonballImpacts.AllocateUnsafely();
_newImpact.cannonball = _cannonball;
if (_impactedCollidable.Mobility != CollidableMobility.Static)
{
    // The filter's group id is the ship's main body handle. We use that to find the ship (if this body is related to a ship at all).
    ref BodyProperties _properties = ref properties[_impactedCollidable.Handle];
    _newImpact.impactedShip = _properties.ship;
    Console.WriteLine($"Hit a ship: {_properties.ship}");
}
else
{
    // It hit a static; ships aren't static
    _newImpact.impactedShip = null;
}

Re: Bepu v2 Collision Detection

Posted: Mon Nov 18, 2019 11:23 pm
by Norbo
Is storing references in a body property a bad idea? Is that what's causing this issue?
Very likely, yes- the runtime gets upset when references are stuck in places it can't track properly.

Buffer<T> should have an 'unmanaged' generic constraint to avoid this, but there were some language compatibility issues. Historically, types with generic parameters were not considered unmanaged. C# 8 addresses this, but I need to confirm there's no blocking issues with .NET Framework before I upgrade the language version.
Is there a way around this?
If you want to keep using the BodyProperty type, you could change the reference to an index or some other piece of metadata that allows you to quickly look up the instance in question.

Another option would be to just not use the BodyProperty type- it's a pretty darn simple helper. You could copy paste it and replace the Buffer<T> with a T[], get rid of the extraneous buffer-related stuff, and just rely on regular old managed memory.

Re: Bepu v2 Collision Detection

Posted: Mon Nov 18, 2019 11:31 pm
by tomweiland
Norbo wrote: Mon Nov 18, 2019 11:23 pm If you want to keep using the BodyProperty type, you could change the reference to an index or some other piece of metadata that allows you to quickly look up the instance in question.
Everything I need to do this is already in place since I use dictionaries to track ships, cannonballs, etc. and they all have their own ID, I can't believe I didn't think of this myself...

Re: Bepu v2 Collision Detection

Posted: Fri Nov 22, 2019 7:10 pm
by tomweiland
Is there a way to get the normal of a collision? Something like the red line in the picture. Since the ship body is quite large, simply grabbing the difference between the 2 objects' centers won't work.
CollisionNormal.png
CollisionNormal.png (4.43 KiB) Viewed 17120 times

Re: Bepu v2 Collision Detection

Posted: Fri Nov 22, 2019 7:34 pm
by Norbo
Yes, the contact manifolds provide the surface normal. Nonconvex manifolds have per-contact normals while convex manifolds have a single shared normal. You can use the IContactManifold.GetContact or GetNormal retrieve the normal if you're dealing with the generic callback.