Sphere collider receives strong impulse on contact with mesh collider edge

As you can observe from the picture 1 and 2, sphere collider receives strong vertical impulse while colliding with an edge, while having normal impulse in the middle of polygon. Previewed in grey is static mesh collider.

When the ball rolls down only by gravity pull, this is barely noticeable, represented as tiny hiccups. But as soon as I add either linear or angular velocity, the ball starts jumping with a greater contact velocity resulting in larger jumps.

Is this an expected behavior, does it have something to do with DOTS Physics being cache-less? If the answer is yes, does it mean I’ll need to alter the results after SimulationCallbacks.Phase.PostCreateContacts ? Any advice much appreciated.

Yes, this is expected with the continuous collision detection approach used in Unity Physics.
This video talks about behavior differences in Unity Physics &Havok Physics for Unity and the ModifyNarrowphseContacts sample handles this usecase in a simple way.

1 Like

Thanks Stevee, LOTS of useful information on links and talks. I have a progress, so I would much appreciate validation of my approach since I believe there is a better way that I couldn’t find. What I wanted to do is the same thing as in ModifyNarrowPhaseContacts example but, since I don’t have flat surface, I need to correct normal on polygon level.

Here is the approach I use:

  1. Generate entity query that schedules IContactsJob instance callback for each entity with surface modificatior
    Step 1 code sample
        var m_BuildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
        var modifiers = m_ContactModifierGroup.ToComponentDataArray<ModifySpeculateveCcdContactPointNormal>(Allocator.Temp);
        // schedule a job for each surface that should modify interaction
        for (int i = 0; i < modifiers.Length; ++i)
        {
            var surfaceRBIdx = m_BuildPhysicsWorld.PhysicsWorld.GetRigidBodyIndex(modifiers[i].surfaceEntity);

            SimulationCallbacks.Callback callback = (ref ISimulation simulation, ref PhysicsWorld world, JobHandle inDeps) =>
            {
                return new ModifyNormalsJob
                {
                    m_SurfaceRBIdx = surfaceRBIdx,
                    world = m_BuildPhysicsWorld.PhysicsWorld
                }.Schedule(simulation, ref world, inDeps);
            };
            m_StepPhysicsWorld.EnqueueCallback(SimulationCallbacks.Phase.PostCreateContacts, callback);
        }
  1. Inside a job, cast ray from dynamic body to contact point in order to get surface normal
    Step 2 code sample
public float3 RaycastNormal(float3 RayFrom, float3 RayTo)
        {
            RaycastInput input = new RaycastInput()
            {
                Start = RayFrom,
                End = RayTo,
                Filter = new CollisionFilter()
                {
                    BelongsTo = ~0u, // all 1s, so all layers, collide with everything
                    CollidesWith = 1u, // track layer
                    GroupIndex = 0
                }
            };

            Unity.Physics.RaycastHit hit = new Unity.Physics.RaycastHit();
            bool haveHit = world.CastRay(input, out hit);
            if (haveHit)
            {
                return hit.SurfaceNormal;
            }
            return float3.zero;
        }
  1. Set surface normal as the new normal of ModifiableContactHeader
    Step 3 code sample
public void Execute(ref ModifiableContactHeader contactHeader, ref ModifiableContactPoint contactPoint)
        {
            bool bodyAIsSurface = contactHeader.BodyIndexPair.BodyAIndex == m_SurfaceRBIdx;
            bool bodyBIsSurface = contactHeader.BodyIndexPair.BodyBIndex == m_SurfaceRBIdx;

            // ignore contacts where m_SurfaceRBIdx does not take part in
            if (!(bodyAIsSurface || bodyBIsSurface)) return;

            float3 dynamicBodyPosition = world.GetPosition(bodyBIsSurface ? contactHeader.BodyIndexPair.BodyAIndex : contactHeader.BodyIndexPair.BodyBIndex);

            var newNormal = RaycastNormal(dynamicBodyPosition, contactPoint.Position);
            distanceScale = math.dot(newNormal, contactHeader.Normal);
            contactHeader.Normal = newNormal;
        
            contactPoint.Distance *= distanceScale;
        }

EDIT: It turned out that the problem described bellow was that rays were a bit to short, so I extended them and I get the hit on the “Inside” part of collider.

I even struggle to make above approach work because it looks like contact points become somewhat inverted during this process as you can see in following pictures. Yellow lines are rays and red lines are surface normals.

“Inside” of mesh collider:

“Outside” of mesh collider:

1 Like

Hmmm…it looks like I solved the problem with normals but that did not solve my bounciness problem because impact is still calculated by the solver and I just redirected it’s resulting direction. It does not look like I can do much about it at this phase and here is why I think so.

When calculating impulse magnitude of the collision event, physics sums impact of all the estimated contact points in a next way:

  • Projects each vector that stretches from center of mass to the contact point (cyan lines) on the corresponding normal (red lines) via dot product

  • Sums this projected magnitudes

  • Divides this sum with the impact force

I’m not sure how the integration goes in order to compute final impact points (blue lines) but it looks like that, because the transversal edge of forthcoming polygon has more than one impact point, it adds up a lot of impact force and hence the bounce.

I believe that correcting contact point distance (length of green arrows) might help resolving this issue, but I have no idea how and to what extent, ATM.

1 Like

@steveeHavok or anyone else from the team that feels like he can provide any help, I wold really appreciate some background info regarding this matter :frowning:

I’m trying to figure out what is the root cause of this “problem” and It feels like I’m kind of beating around the bushes but cant make the lizard go out of it…I love lizards.

Here are some things I’m struggling to see the clear picture about by examining the source code:

  • Is the solver integrating all the ContactPoints across all the ContactHeaders or is it finding the ContactHeader that contains closest ContactPoint to the impact (smallest Distance) and integrate all the contact points in that header?
  • It seams like all the ContactHeaders are vertex contacts (have value of 1 for NumContacts). Is this something intentional? It feels, by the picture above, that there are 3 contact points belonging to the same edge, what kind of makes me thinking that there is something to it, that I could resolve it by dropping header if it’s an edge, or removing some contact points.

Just a note that I’m currently moving the ball via angular velocity, but I’ve tried with linear at some point and it behaved in a similar manner.

1 Like
  1. Yes, the solver looks at every contact point of every header, it doesn’t choose anything
  2. For a sphere collider that is expected, in your particular case you have a mesh with triangles, and every sphere-triangle pair produces a contact header (because headers work on collider key pairs) and a contact point (because all you need to separate a sphere and a triangle is a single contact point).

I took a brief look at what you are doing with your fixup of contact points, and casting a ray towards the point does produce a better normal, but not good enough. Could you instead try to cast the ray (or even better the collider) along the direction of movement, and if no hit it’s obviously a ghost plane, but if there’s a hit that should give you a much better normal. Does that sound like something you would like to try?

Thanks for the response, I guessed that it is because of the way the sphere is represented and I even thought about using the “real” polygonized sphere but decided to try make it work this way for the sake of efficiency.

I’ll try literally anything since I’m kind of loosing ideas of how to approach this any more :eyes:

What is the idea behind collider cast?

If I cast the collider along the linear velocity vector, get the hit, and then use its point to somehow decide if I should disable contact point, I might end up with no “supporting” contacts on flat segments so the ball will fall through the mesh collider, right?

On the other hand, if I use that hit point of collider cast and then set all the contact points calculated by the narrow phase to be at that point, an probably adjust distance somehow, time to impact might get to long so the narrow phase impulse estimation ignores it.

Or I might be getting your idea wrong.

What is REALLY interesting observation, and I’ve been observing this thing A LOT, is that it looks like it uses orientation of the edge relative to the linear velocity, so it gets bumped only on perpendicularish edges. This is just an really intuitive observation and I could not really find part of code that describes this behavior.

What do you think about this idea:

  • Find the normals that are opposing linear velocity vector (those should be normals of forthcoming edge)
  • Align those normals with the mesh surface in the direction of linear velocity

The idea behind collider cast is exactly what you described in the end - to detect normals that are opposing the linear velocity vector and align them. You shouldn’t even disable a contact point, just fix up the normal so that it’s not “ghost”. Also you won’t be getting fall through the ground because your linear velocity vector incorporates some downwards velocity (coming from gravity) so the direction will be pointed downwards to some extend and it will pick up ground hits.

Great! I’ll play with it and come back with the results.

One thing before I immerse in this adventure…it seams to me that we might not be watching this from the same point of view so I would like to align them to avoid pitfalls.

I’m scheduling a job that handles this whole welding logic in a job that is scheduled like so

m_StepPhysicsWorld.EnqueueCallback(SimulationCallbacks.Phase.PostCreateContacts, callback)

so I already have all the contact headers/points and I’m doing raycasts inside that job only to get the surface normals (which I’ll need anyway for gravity factor correction but that’s other matter). So what is idea with collider cast at that point in time, I thought about just using the pre-calculated normals from narrow phase and not doing raycasts at all?

This should work if I leave the contacts as they are, but my whole idea about resolving this is actually about adjusting contact normals/distances - the green arrows - in order to avoid “ghosts”, please point me in correct direction if I’ve approached this whole thing from the wrong angle.

Maybe you don’t even need a full collider cast, but just a simple “sub-integration” to the normal and then do another sphere vs. whatever collider is there to fixup the normal… The normal is what is bothering you here, you don’t really need to touch the contact points I believe.

But before that, I just realized, have you looked at the https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/UnityPhysicsSamples/Assets/Demos/5.%20Modify/ModifyNarrowphaseContacts.unity demo in the samples? And more importantly, this script https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/UnityPhysicsSamples/Assets/Demos/5.%20Modify/Scripts/ModifyNarrowphaseContactsBehaviour.cs? It is doing what you are trying to achieve, and at the exact same point in time you are doing it.

I believe you are correct, when I say contact point, I usually refer to normal in contact header originating from contact point, since there is single contact point per contact header so the contact point only really adds length to the normal.

Yes, I used narrow phase modification sample as a starting point. On the last image above, I did practically the same thing, correcting normals to align with polygon normals and scaling them by projecting original normal to the new, perpendicular to surface, normal (dot product basically). The only thing I did differently was raycast, because my surface is not flat so I couldn’t just assign vector.up.
But it maid the things even worse, since now there are no two opposing vectors (linear velocity and impact vector) thus making resulting impulse gaining a bit steeper angled and propelling the ball higher in the air.

Hi, regarding ModifyNarrowphaseContactsBehaviour, I’ve implemented a partial solution based on that. I posted about it in this thread . The main issues with it are that it doesn’t stop bumps between adjacent colliders and that I found the balls didn’t play nicely with concave colliders such as a tunnel. Also you have to alter the Physics package to make ConvexHull public.

I think I came across your solution but I wanted to avoid modifying physics so I ended up casting ray instead of accessing underlying data, you can see code sample above in one of my answers. But that made it even worse in my case if you look at the previous answer. Have you managed to make it work somehow? Something else than just correcting normal angle/distance?

Unfortunately no, this is the best I could do. Frankly, I’m out of my depth here.

1 Like

I hope I understand the issue properly and that content below will unblock you.

First of all I’ll try to reduce your case to the image(s) below
5972531--640949--Welding1.png
a is contactPoint.Position (It’s a world position of contact point on body A)
b is contact point on triangle 2 of body B
c is position of body A

So first version of your ray that didn’t have a hit was from c to a and when you expanded it you managed to hit body b. Luckily for sphere a, b and c are colinear so extended ray was hitting the mesh in b (or around b due to numerical inaccuracies.

Next thing with the ray is that it does a cast on a world level so the hit can happen on either triangle 1 or triangle 2. It can be considered that as if triangle being hit is random from those two triangles. So the resulting normal may be the blue one which means that penetration can happen and then penetration recovery can bounce the sphere off. If b is a vertex shared between the triangles the result can just be worse.

It’s easy to create a situation where wrong triangle being hit can result in a ghost collision.5972531--640952--Welding2.png
So if ray hits triangle 1 we get the ghost collision on the blue normal. Problem is that if ray hits triangle 2 and we get the green normal, the sphere is actually already in a penetration of that normal and penetration recovery kicks in right away.

I hope that this discourages everyone from using a raycast toward the contact point b on B :slight_smile:

Now something about the potential solution.

As @petarmHavok proposed the collider should be casted and let’s consider how that helps. If we think about case of avoiding ghost collision and having a hit we will end up with following thing:
5972531--640964--Welding3.png
so we will cast body A from c to c’. The movement is just the velocity * dt. At the point of the callback the velocity of the body has already been adjusted by gravity effect in Unity.Physics. So cast hit will return the green normal and everything is fine.

Lets check the second case with the same approach:
5972531--640970--Welding4.png
We are doing the same thing just this time there is no hit. Since there is no hit we need to create some normal on our own. If we just disable the contact the triangle 2 may stay “unguarded” which may not be much of a problem on this diagram but in general it can be. So in this case we should form the green separation plane which is parallel to the movement vector. We shouldn’t rotate the plane more than that because overshooting may result in body A ending up in the immediate penetration like on the second diagram.

Important: Instead of doing the cast on the world level and ending up with the same normal for all contact points the collider cast has to be done on ((bodyA, colliderKeyA), (bodyB, colliderKeyB)) pair level. That is the only way to make sure that all original contacts are represented with proper ones after the cast.

How to do this? For this we need to do something similar to what @mpforce1 did. Luckily there is no need to change the physics code. This time we get the leaf collider through collider.getLeaf(key, out targetLeaf) and then we use the result as a target for the cast and call targetLeaf.Collider->castCollider(). Unfortunately this is unsafe. I hope that you are find with it.

Also, the input for castCollider has to be in the targetLeaf.Collider’s space.

I hope that this helps. I haven’t written the code myself although we should definitely provide some referenced implementation. Welding is a very hard problem and there is no solution for it that fits all the cases. Havok has been building solution for welding for a decade and it’s still not perfect, but still, using Havok has high probability of solving the welding problem for most of the games. I hope for those who cannot afford Havok this post gives enough information and reasoning around welding to try to implement solution that covers all their cases.

2 Likes

WOW! This is a information treasure both for the proposed solution and for the pieces I’ve been missing in order to understand the root cause of the problem. Thanks a lot for the time and effort to write and draw this. I love the pictures and samples, reminds me of Project Anarchy documentation back in the days :slight_smile:

So, practically speaking, in order to check for the potential penetration, body A is moved along the “contact axes” so the point a and point b align, and then is performed “penetration check”? And the shared vertex is probably the reason from my observation about why the perpendicular edges result in so strong impulse. There is, however, only one thing left unclear and that is why we have 3 blue impulse vectors on the last picture I shared, why would we have more then one at any point in time, apart from when there is penetration? Those are vectors drawn by physics debug, btw.

I’ll fallow your proposal and keep you updated if I solve it or, potentially, hit the next bump. Thanks again!

My aim is to build competitive mobile game with ball physics as it’s core mechanics so that is why I chose Unity Physics, the promised cross-platform determinism.

Each contact point of body A has corresponding contact point on body B but only contact points of body A are important. Solver treats bodyB as static and uses relative velocity of bodies as a body A velocity and then it just tries to prevent any contact point on body A to penetrate separating normal by applying impulse on each contact point that may go through separating plane.
That process is applied on each separating plane. In your case this means between sphere and each triangle. Order of solving the pair (sphere-triangle) can be considered as arbitrary (but deterministic).

To get back to your question. I am not sure how those impulses are created since they are drawn on contact points on traingles, not on a sphere. It looks like that rays are shot from the sphere position at the beginning of the frame and that screenshot is at the end of the frame when sphere didn’t touch even a ghost plane (At least that’s how i read the image).

Maybe we shouldn’t debug the current state before you move to new approach.

1 Like

@Sima_Havok I’m having some trouble getting the collider cast to work.

My Code

private readonly struct Details {
    public readonly float3 Position;
    public readonly quaternion Rotation;
    public readonly float3 Velocity;
    public readonly ColliderKey Key;
    public readonly BlobAssetReference<Collider> RootCollider;

    public Details(PhysicsWorld world, int index, ColliderKey key) {
        var body = world.Bodies[index];
        Position = body.WorldFromBody.pos;
        Rotation = body.WorldFromBody.rot;
        Velocity = world.GetLinearVelocity(index);
        RootCollider = body.Collider;
        Key = key;
    }
}

private unsafe bool TryFindSurfaceNormalByColliderCast(
    ref ModifiableContactHeader contactHeader,
    out float3 surfaceNormal
) {
    var pair = contactHeader.BodyIndexPair;
    var keys = contactHeader.ColliderKeys;
    var a = new Details(PhysicsWorld, pair.BodyAIndex, keys.ColliderKeyA);
    var b = new Details(PhysicsWorld, pair.BodyBIndex, keys.ColliderKeyB);
    if (IsBallAndScenery(a, b, out var scenery, out var ball)) {
        scenery.RootCollider.Value.GetLeaf(scenery.Key, out var sceneryLeaf);
        ball.RootCollider.Value.GetLeaf(ball.Key, out var ballLeaf);
        var inverseTargetRotation = math.inverse(scenery.Rotation);
        var ballPositionInTargetSpace = math.mul(inverseTargetRotation, ball.Position);
        var ballMovementInTargetSpace = math.mul(inverseTargetRotation, ball.Velocity * DeltaTime);
        var ballRotationInTargetSpace = math.mul(inverseTargetRotation, ball.Rotation);
        var endPosition = ballPositionInTargetSpace + ballMovementInTargetSpace;
        if (sceneryLeaf.Collider->CastCollider(
            new ColliderCastInput {
                Collider = ballLeaf.Collider,
                Start = ballPositionInTargetSpace,
                End = endPosition,
                Orientation = ballRotationInTargetSpace
            }, out var hit
        )) {
            Debug.Log("Hit found");
            surfaceNormal = hit.SurfaceNormal;
            return true;
        } else {
            // TODO
            surfaceNormal = default;
            return false;
        }
    } else {
        surfaceNormal = default;
        return false;
    }
}

As you can see I’m trying to translate the ball position and rotation into the target’s space but I’m not sure I’m doing it right because I never hit that debug log. Furthermore, the ball and scenery are both just in world space so translating them this way doesn’t actually change the values. Additionally, I’ve found through debugging that I always hit this in Unity.Physics.ColliderCast.cs (line 165):

ColliderCast.cs

// Check for a miss
float dot = math.dot(distanceResult.NormalInA, input.Ray.Displacement);
if (dot <= 0.0f)
{
  // Collider is moving away from the target, it will never hit
  return false;
}

Which suggests that it thinks the ball is moving away from the target which isn’t true, in this case it’s resting on and thus falling towards the target due to gravity. Do you have any idea what I’m doing wrong here?

The problem is in your collider cast - you did transform the start and end to target space, but only accounted for rotation. There is also a translation part of it. So you need to do a full transform, not just rotate.

You can take a look at RigidBody.CastCollider(), that’s exactly what happens there (in latest Unity Physics package, previously it was a bit more hidden).

@petarmHavok Thanks for responding. Are you referring to an unreleased version of the physics package? I think I’m running with the latest version (0.3.2-preview) and all I can see is this (line 61):

public bool CastCollider(ColliderCastInput input) => QueryWrappers.ColliderCast(ref this, input);
public bool CastCollider(ColliderCastInput input, out ColliderCastHit closestHit) => QueryWrappers.ColliderCast(ref this, input, out closestHit);
public bool CastCollider(ColliderCastInput input, ref NativeList<ColliderCastHit> allHits) => QueryWrappers.ColliderCast(ref this, input, ref allHits);
public bool CastCollider<T>(ColliderCastInput input, ref T collector) where T : struct, ICollector<ColliderCastHit>
{
    return Collider.IsCreated && Collider.Value.CastCollider(input, ref collector);
}

At any rate, is the solution to simply subtract the scenery.Position from ballPositionInTargetSpace?