bepu / bepuphysics2

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

no collision with mesh colliders #258

Open nablabla opened 1 year ago

nablabla commented 1 year ago

Hi, I am un 2.3.4 I tried to folow the MeshTestDemo.cs (and DemoMeshHelper.cs) as close as possible. My problem is that my MeshColliders do not collide with BoxColliders, the BoxColliders collide with each other and all Bodies fall down (gravity works).

I created a minimal example in form of a tetrahedron

var x = new System.Numerics.Vector3(0, 0, 1);
var y = new System.Numerics.Vector3(-1, 0, -1);
var z = new System.Numerics.Vector3(1, 0, -1);
var t = new System.Numerics.Vector3(0, 1, 0);

Triangles = new Triangle[4];
Triangles[0] = new Triangle(x, y, z);
Triangles[1] = new Triangle(x, t, z);
Triangles[2] = new Triangle(z, t, y);
Triangles[3] = new Triangle(y, t, x);

bufferPool.Take<Triangle>(4, out var triangles);
for (int i = 0; i < 4; i++)
{
    triangles[i] = Triangles[i];
}
var physicalMesh = new Mesh(triangles, System.Numerics.Vector3.One, bufferPool);
physicalMesh.ComputeClosedInertia(1, var out inertia);

// here I have the <2.4.0 collidable descriptions
CollidableDescription description = new CollidableDescription(
    Simulation.Shapes.Add(physicalMesh), 0.2);

  var bodyDescription = BodyDescription.CreateDynamic(
      Entity.Transform.Position, inertia, description, new BodyActivityDescription(0.01f));

  Simulation.Bodies.Add(bodyDescription)

The whole thing adds a lot like graphics, I extracted all I find that is relevant.

Simulation = Simulation.Create(
                bufferPool,
                new NarrowPhaseCallbacks(),
                new PoseIntegratorCallbacks(new Vector3(0.0f, -10.0f, 0.0f)),
                new PositionLastTimestepper());
 public struct NarrowPhaseCallbacks : INarrowPhaseCallbacks
        {
            public void Initialize(Simulation simulation)
            { }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b)
            {
                return true;
            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB)
            {
                return true;
            }
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool ConfigureContactManifold<TManifold>(
                int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial)
                where TManifold : struct, IContactManifold<TManifold>
            {
                // TODO: use collider properties here!!
                pairMaterial.FrictionCoefficient = 1f;
                pairMaterial.MaximumRecoveryVelocity = 2f;
                pairMaterial.SpringSettings = new SpringSettings(30, 1);
                return true;
            }

            public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold)
            {
                return true;
            }

            public void Dispose()
            { }
        }

        public struct PoseIntegratorCallbacks : IPoseIntegratorCallbacks
        {
            Vector3 Gravity;
            Vector3 gravityDt;

            public PoseIntegratorCallbacks(Vector3 gravity) : this()
            {
                Gravity = gravity;
            }

            public void Initialize(Simulation simulation)
            { }

            /// <summary>
            /// Gets how the pose integrator should handle angular velocity integration.
            /// </summary>
            public AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving;

           public void PrepareForIntegration(float dt)
            {
                //No reason to recalculate gravity * dt for every body; just cache it ahead of time.
                gravityDt = Gravity * dt;
            }
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public void IntegrateVelocity(int bodyIndex, in RigidPose pose, in BodyInertia localInertia, int workerIndex, ref BodyVelocity velocity)
            {
                //Note that we avoid accelerating kinematics. Kinematics are any body with an inverse mass of zero (so a mass of ~infinity). No force can move them.
                if (localInertia.InverseMass > 0)
                {
                    velocity.Linear += gravityDt;
                    velocity.Linear *= 0.99f; // linear dampening
                }
            }

        }

Any idea, why the bodies with mesh colliders fall through the static box collider floor (all bodies with box colliders work)? Any pitfalls that I might have stumbled over?

RossNordby commented 1 year ago

Meshes are composed of triangles and triangles are one sided; collisions on the backface of a triangle go through.

By the way, standard recommendation is avoid mesh-mesh collisions. They're expensive and, because triangles are infinitely thin, will struggle to generate good contacts with each other when approaching resting states (two infinitely thin planes will just slip through each other). Avoid meshes in dynamics whenever possible for this reason.

nablabla commented 1 year ago

Yes i tried both directions, and both have the same problem. I managed to get the manually generated tetrahedron to collide by making the floor thicker. I am surprised that 0.1 is not enough, the speed was not that great. A simple cube mesh loaded from file also works if the floor is thicker. However a slightly more complex mesh does not work, no matter what I do.

RossNordby commented 1 year ago

The original code has inconsistently wound triangles; the bottom face is flipped from the other faces. When I run it on master with consistent windings, it works as expected. The other more complex meshes may have inconsistent windings.

Note that a triangle parallel to the surface of a box, falling onto a box, with a box thickness of 0.1, requires high contact stiffness to stop the collision from allowing more than 0.05 units of penetration because triangles are infinitely thin. The common demos setting of 30hz frequency, 1 damping ratio, and 2 maximum recovery velocity is not stiff enough to handle that.

Increasing the maximum recovery velocity and frequency may help, though be advised that it can be harder to solve that kind of stiffness in complex constraint systems (like lots of objects in a tall stack). It may require more iterations, a faster timestep frequency, or substepping.

nablabla commented 1 year ago

Yes, It worked when I did this again with correct winding. Also I increased the floor to 2.0f

Now only compounded shapes of meshes fall through, is that not allowed? Can I:

Or is that problematic in some way because I add the shape to the simulation twice? I want to have a generic class that returns colliders, either shapes or collidableDescriptions, but IShape can't go because it is always required to be unmanaged. I want to put it into a simple demo but the v2 interface just keeps me from doing anything atm. The fact that IShape can't be a generic return value hinders

RossNordby commented 1 year ago

Or is that problematic in some way because I add the shape to the simulation twice?

Compounds can only refer to child shapes which have been added to the Simulation.Shapes collection, so that part is fine. The problem is that compounds can only contain convex shapes, and Mesh is not convex.

(I explicitly disallowed compound nesting because you're generally better off performance-wise flattening the hierarchy. I don't do that flattening automatically in the library because nonoptional "magic" stuff almost inevitably backfires. Technically, this means you don't get boundary smoothing of Mesh when you flatten its triangles into a BigCompound, but this type of compound grouping is primarily useful for dynamic bodies, and the strong recommendation "don't use meshes in dynamic bodies" stands. Compounds of convex hulls generated through mesh convex decomposition is the way to go for that use case.)

I want to have a generic class that returns colliders, either shapes or collidableDescriptions, but IShape can't go because it is always required to be unmanaged.

Not sure I understand the use case here. IShape instances passed to the library must be of unmanaged type, yes; all shape data is stored in unmanaged buffers. If you want a type-agnostic handle for a shape, the TypedIndex returned by Simulation.Shapes.Add(...) would work. That's what the CollidableDescription holds.

nablabla commented 1 year ago

Compounds can only refer to child shapes which have been added to the Simulation.Shapes collection, so that part is fine. The problem is that compounds can only contain convex shapes, and Mesh is not convex. (I explicitly disallowed compound nesting because you're generally better off performance-wise flattening the hierarchy. I don't do that flattening automatically in the library because nonoptional "magic" stuff almost inevitably backfires. Technically, this means you don't get boundary smoothing of Mesh when you flatten its triangles into a BigCompound, but this type of compound grouping is primarily useful for dynamic bodies, and the strong recommendation "don't use meshes in dynamic bodies" stands. Compounds of convex hulls generated through mesh convex decomposition is the way to go for that use case.)

dang it >_< Okay, does it say that anywhere? I just assumed it works because the method signatures allow it. Also no warnings. I don't have a specific use case for anything yet, just building it into our engine. Can you promt some kind of warning for that case? Now(2.3) it appears to just ignore that and have no collisions.

Not sure I understand the use case here. IShape instances passed to the library must be of unmanaged type, yes; all shape data is stored in unmanaged buffers. If you want a type-agnostic handle for a shape, the TypedIndex returned by Simulation.Shapes.Add(...) would work. That's what the CollidableDescription holds.

Yes, I pass the CollidableDescription around. I was just not shure about it, because in order to create it, the shape must be added to the sim, while in this example the shapes are added directly (https://github.com/bepu/bepuphysics2/blob/v2.3.0/Demos/Demos/CompoundTestDemo.cs: 37+38). So I assume they will then be added by the builder.

RossNordby commented 1 year ago

Can you promt some kind of warning for that case? Now(2.3) it appears to just ignore that and have no collisions.

I don't remember when they were added, but the latest version does have a couple of forms of validation.

Pretty sure 2.3 had some validation for this (though 2.3 is old). Note that some validation is only active when running in debug mode with source.

So I assume they will then be added by the builder.

Yes, it's a very simple and shallow convenience function.