Is it me, or is defaultContactOffset wrong?

From my understanding of the Default Contact Offset, if the distance between the collision boxes has a sum less than the offset, it flags the collision.

I have a setup with cubes of unit size.
The floor cubes have a BoxCollider with size (1, 1, 1).
The player cube has a BoxCollider with size (0.979, 0.979, 1).
My defaultContactOffset is set to the default of 0.01f.

Each cube is centred to the unit (0, 0, 0 | 0, -1, 0 | 0, -1, -1 etc) and the player cube's Rigidbody is not affected by rotation and gravity.

Yet, when the player moves forwards, it collides with the floor; even though there should be a 0.0105f distance between the colliders.

To get a reliable result, I need to set the player's BoxCollider size to (0.96, 0.96, 1); an offset of 0.04f and a distance of 0.02f between the colliders.

From the manual:
[quote]
Colliders whose distance is less than the sum of their contactOffset values will generate contacts.
[/quote]

Note it only talks about generating contacts but doesn't say anything about changing the contact's values (specifically the point they're generated at) or resolving collisions. It just says it will generate contacts. Then it explains why:

[quote]
The value must be positive, and if set too close to zero, it can cause jitter. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated.
[/quote]

So objects within the default contact offset will generate a contact constraint, to prevent jittering due to objects at rest colliding on one frame, going out of collision, then colliding the next frame, then going out of collision, etc. Collision resolution (that is, depenetration) is still only enforced when the colliders physically overlap.

But in the above example, there is no overlap and yet the collision is enforced?

Not sure I understand, you mentioned the opposite: [quote]
Yet, when the player moves forwards, it collides with the floor; even though there should be a 0.0105f distance between the colliders.
[/quote]

I was under the impression that the player is touching the floor even though you'd expect them to be separated?

Yes, you said "Collision resolution (that is, depenetration) is still only enforced when the colliders physically overlap.".
If there's a 0.0105f distance between the objects, why is a collision being triggered at all? There's no overlap...

When you say "collision being triggered" do you mean actual collision resolution, or just a callback like OnCollisionEnter?

As far as I'm aware, OnCollisionEnter will fire as soon as objects are within defaultContactOffset. Also if you're using continuous speculative contacts, this might happen way before the contact offsets actually overlap, due to contacts being generated when there's a chance objects will overlap at the end of the frame even if they end up not actually overlapping.

See attachments:

The top cube has the components shown.
The bottom cube is a 3D cube with a BoxCollider of size (1,1,1).

This triggers OnCollisionEnter in top cube's "Example".

9858960--1420032--upload_2024-5-28_10-45-18.png
9858960--1420038--upload_2024-5-28_10-46-4.png

Ok, so what would you expect?

The top cube is 0.979 units in the X and Z axis, but 1 unit in the Y axis. It's on top of the bottom cube so assuming the bottom cube is at 0,0,0 and the top cube at 0,1,0, they are within each other's default contact offset in the Y axis so OnCollisionEnter should be called.

It's 0.979 in X & Y, not X & Z.
And if I change the Default Contact Offset to 0.001f, instead of 0.01f, there's no trigger.

My mistake, sorry. I meant X and Y.

bottom cube's top face Y coordinate (transform.y + half its height + contact offset)
0 + 0.5 + 0.01 = 0.51

top cube's bottom face Y coordinate (transform.y - half its height - contact offset)
1 - 0.4895 - 0.01 = 0.5005

As you can see the top cube's bottom face is below the bottom cube's top face, so they overlap. If you change the contact offset to 0.001, they no longer overlap. I believe the results you're getting are correct.

If you're saying that both colliders get the offset applied then 0.96 should work, except that doesn't either (error in first post).

"Colliders whose distance is less than the sum of their contactOffset values will generate contacts."

0.96 would make it 0.02; the sum of the offset values. This still triggers OnCollisionEnter with 4 contacts.

And on a curiosity note, if the offset's getting applied to both colliders anyway, why can't we use the direct collision provided by the bounds?


I'm not following. In your first post you mention that the only way you've found to get reliable results (by "reliably" I guess you mean OnColliderEnter not being called?) is by using 0.96.

0.96 would place the top of the bottom cube and the bottom of the top cube exactly right on top of each other:

0 + 0.5 + 0.01 = 0.51
1 - 0.48 - 0.01 = 0.51

but floating point equality comparisons will often fail due to precision issues. Does it stop calling OnCollisionEnter if you use 0.959 instead?

Yeah, I realised a while after posting that 0.96 wasn't entirely reliable. 0.959 is fine.
So defaultContactOffset is applied to all colliders and the collisions flagging at 0.96 are likely due to floating point errors.

Still not entirely sure why the offset's on both Colliders. Surely that's just giving your collider a collider? But I'm not a physics engineer

Thank you for helping me wrap my head round this. Unwanted collisions have been getting in the way of my sliding block game to the point where I'm basically doing manual checks. Glad to get an inkling as to why.

The default contact offset is a global setting, so it applies to all colliders in the scene. Why would it be applied to some colliders but not others?

The idea behind this is that contact and collision are two separate concepts. Think of a contact as a small object that holds a reference to 2 colliders, and the distance between their closest points.

When the physics engine believes two objects might end up overlapping in the near future, it generates a contact between them. One of the parameters that controls this is the default contact offset: colliders that are within default contact offset generate contacts, simply because they are deemed close enough to potentially collide. This also calls OnCollisionEnter as they are about to collide (there's a small chance they won't, but you can check this inside OnCollisionEnter to ignore invalid contacts).

During solving, contacts that turn out to have a negative distance (that is, their 2 colliders are actually overlapping) calculate and apply an impulse to them to take them out of each other. As there may be many contacts involved in the system and they are solved one by one, "solving" some contacts may make other contacts worse (increase their overlap), or it might cause objects that should be touching each other to separate incorrectly. For this reason, even contacts with a positive distance may end up applying impulses to their 2 objects. This is also why it's important to generate contacts before the objects are actually inside each other.

hope this helps!

To clarify what I meant regarding the offset being applied to both Colliders, I meant that in my head it seems easier to define the offset as an addition to the distance calculation.
Take Circle Colliders:
If (Vector3.Distance(posA, posB) <= circleARadius + circleBRadius + offset)
Debug.Log("Close Enough");

Effectively doing what defaultContactOffset set to 0.005f achieves.

CircleCollider2D are 2D and the contact offset isn't used for circle shapes. In 2D it's used for all polygons as a skin surrounding polygons, it's not some kind of "add on top" thing after contacts are calculated.

Maybe you were refering to SphereCollider as it seems you're talking about 3D which is nVidia PhysX which potentially uses it differently; not that I'm an expert there but you can look contact offset up in the PhysX docs where it applies to shapes.

https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/AdvancedCollisionDetection.html

"Shapes used for contact generation influence the motion of the dynamic rigid bodies they are attached to through contact points. The constraint solver generates impulsive forces at the contact points to keep the shapes resting or moving without passing through each other. Shapes have two important parameters that control how collision detection generates contact points between them, which in turn are central for their behavior when colliding or stacking: contactOffset and restOffset. They are set using PxShape::setContactOffset() and PxShape::setRestOffset() respectively. Neither of these values is used directly. Collision detection always operates on a pair of potentially colliding shapes, and it always considers the sum of the offsets of the two shapes. We call these the contactDistance and restDistance respectively."

In the end, it is what it is and it's PhysX defined behaviour, not Unity. It's not "wrong". :)

The Circle Collider wasn't referring to Unity's Circle Colliders. I just figured using two radii and an offset would get my point across about how I believed the defaultContactOffset was applied. :)

That's fine but in a Unity forum, a CircleCollider clearly means a specific thing hence me mentioning it.

Hopefully the other information you never mentioned helps though.