bepu / bepuphysics2

Pure C# 3D real time physics simulation library, now with a higher version number.
Apache License 2.0
2.35k stars 271 forks source link

Slight off-axis rotation on capsule shape results in collisions to be randomly lost #144

Closed Frooxius closed 3 years ago

Frooxius commented 3 years ago

Hello! I've managed to stumble upon yet another edge case while testing ^^;

I have found that applying a very slight rotation to a capsule (used as character controller) can result in contacts being randomly missed and reacquired in some cases. It might affect other shapes as well, but I haven't tested those.

For all the tests I'm using Capsule with following dimensions: new Capsule(0.2f, 1.85f)

Reproduction steps - Capsule - Mesh (quad)

First add a simple quad mesh to CharacterDemo.cs:

 BufferPool.Take<Triangle>(2, out var triangleBuffer);

            triangleBuffer[0] = new Triangle(
                new Vector3(-5f, 0f, -5f),
                new Vector3(+5f, 0f, -5f),
                new Vector3(+5f, 0f, +5f));

            triangleBuffer[1] = new Triangle(
                new Vector3(-5f, 0f, -5f),
                new Vector3(+5f, 0f, +5f),
                new Vector3(-5f, 0f, +5f));

            var mesh = new Mesh(triangleBuffer, Vector3.One, BufferPool);
            var meshCollidable = new CollidableDescription(Simulation.Shapes.Add(mesh), 0.1f);

            Simulation.Statics.Add(new StaticDescription(new Vector3(-20f, 1f, -20f), Quaternion.Identity, meshCollidable));

Then modify the initial pose of the CharacterController body in CharacterInput.cs to have this slight orientation:

var pose = new RigidPose(initialPosition, Quaternion.CreateFromYawPitchRoll(0f, 1.745329E-05f, 0f) );
            bodyHandle = characters.Simulation.Bodies.Add(BodyDescription.CreateDynamic(pose, new BodyInertia { InverseMass = 1f / mass }, new CollidableDescription(shapeIndex, speculativeMargin), new BodyActivityDescription(shape.Radius * 0.02f)));```

I've also shoved in a bit of extra console logging to just print out when the character is supported into UpdateCharacterGoals method:

```CSharp
Console.WriteLine($"Supported: {character.Supported}, Support: {character.Support}");

With this, if you jump on the quad, in some areas (typically near the edges/corners of the triangle) the character will start loosing and reacquiring the contacts. This results in little bouncing, although it's a bit too small to see in this reproduction case, but the logging of character.Supported will flicker between true and false.

Another case - Compound (Capsule) - Cylinder

I found there's another way to reproduce this against a primitive shape when the capsule is a child of a compound (I'm using those to apply an offset from the center).

In addition to the setup above (I'm including it separately, since the first one can reproduce it without needing to add the Compound), I've added this into constructor of CharacterInput:

characters.Simulation.BufferPool.Take(1, out Buffer<CompoundChild> children);
            ref var child = ref children[0];
            child.LocalPose = new RigidPose(new Vector3(0f, 0.85f, 0f));
            child.ShapeIndex = shapeIndex;

            var compound = new Compound(children);

            shapeIndex = characters.Simulation.Shapes.Add(compound);

Then add a big cylinder into the CharacterDemo world:

var cylinder = new Cylinder(8.5f, 0.002f);
            var cylinderCollidable = new CollidableDescription(Simulation.Shapes.Add(cylinder), 0.1f);

            Simulation.Statics.Add(new StaticDescription(new Vector3(0f, 1f, -20f), cylinderCollidable));

Walking on this cylinder will result in the same behavior.

Possible clue - bounding boxes?

I did some investigation seeing if I can fix this myself and I found a workaround. It seems that the issue might be caused by bounding box checks possibly missing by a hair?

If I go to Capsule.cs and update the ComputeBounds:

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ComputeBounds(in Quaternion orientation, out Vector3 min, out Vector3 max)
        {
            QuaternionEx.TransformUnitY(orientation, out var segmentOffset);
            max = Vector3.Abs(HalfLength * segmentOffset) + new Vector3(Radius);
            max *= 1.01f; // HACK expand it a little for test
            min = -max;
        }

And the wide variant:

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void GetBounds(ref QuaternionWide orientations, int countInBundle, out Vector<float> maximumRadius, out Vector<float> maximumAngularExpansion, out Vector3Wide min, out Vector3Wide max)
        {
            QuaternionWide.TransformUnitY(orientations, out var segmentOffset);
            Vector3Wide.Scale(segmentOffset, HalfLength, out segmentOffset);
            Vector3Wide.Abs(segmentOffset, out segmentOffset);

            //The half length extends symmetrically along positive local Y and negative local Y.
            Vector3Wide.Add(segmentOffset, Radius, out max);
            Vector3Wide.Scale(max, new Vector<float>(1.01f), out max); // ALSO HACK oh no!
            Vector3Wide.Negate(max, out min);

            maximumRadius = HalfLength + Radius;
            //The minimum radius is capsules.Radius, so the maximum offset is simply the half length.
            maximumAngularExpansion = HalfLength;
        }

The issue disappears. If I go to Compound and replace both the Tree.Intersects and BoundingBox.Intersects tests with just true (naughty, I know! D: ) it gets fixed in certain cases, but not in others.


I'm not fully sure how to fix this properly myself right now. If expanding the bounding boxes by a bit is ok to do like this or there's a more underlying issue. It works as a workaround for me right now though at least, but if you have a good fix for this I'll definitely take that! ^^; And thank you again for your quick responses and fixes with all of these issues, you're really awesome!

RossNordby commented 3 years ago

Reproduction steps - Capsule - Mesh (quad)

I revamped the capsule-triangle test's handling to deal with the numerical corner case better in 4ff285e832f5286d32bce6d68936111a04829fed; should help.

Another case - Compound (Capsule) - Cylinder

This ended up being caused by the same problem you reported before- apparently the loosened threshold needed to be a little bit looser still: eaf880bef1069dbc99f35e6bc21398a0c8cd3cce

And thank you again for your quick responses and fixes with all of these issues, you're really awesome!

Thank you for the reports, and keep 'em comin'! This is very helpful for hammering out those last finicky bits.

Frooxius commented 3 years ago

Aaah thank you so much! I've tested it with our community and it seems to have fixed all the collision weirdness that we found! I think this issue is good to close from my end!

And nice, I'm glad it's helping! I was a bit worried that I'm bringing too many. We've got a lot of user content with all kinds of weird stuff, so it ends up hitting a lot of those corner cases! :D It's good to make things nice and robust though, I'll bring more if we find more stuff!