Understanding Physics2D.BoxCast offsets - not just defaultContactOffset

I tried switching a Raycast to a BoxCast in my character controller, and ran into some bugs which I tracked down to BoxCast returning a shorter Distance, with a constant offset.

I expected to find that this offset was defaultContactOffset, or double it. I ran some experiments box casting against a box collider, and found that actually the offset is given by 0.0075 - 0.5 * defaultContactOffset. That was unexpected.

Casting against a circle collider instead, I seem to get different results. In fact, whilst it undershoots against a box collider, it overshoots against a circle collider.

Is this behaviour well understood or documented somewhere? Can anyone explain why it’s given by that expression? And is this reliable across situations, versions, and devices?

My conclusion here is that BoxCast is pretty much useless for precise collision detection due to these quirks. Is that reasonable?

Any kind of polygon (a box is a polygon too) uses offsets which is why circle/circle doesn’t have any. The results returned are not meant to be a pure intersection test because the physics engine (Box2D) doesn’t do that. It returns where it considers it touching as would a continuous collision contact.

Raycast isn’t used by the physics engine at all nor is overlap-point (for example) and those are just a pure intersection test.

All the shape casts use this Box2D method (ignoring extra details): box2d/src/collision/b2_time_of_impact.cpp at 411acc32eb6d4f2e96fc70ddbdf01fe5f9b16230 · erincatto/box2d · GitHub

You can see a target/tolerance here:

float totalRadius = proxyA->m_radius + proxyB->m_radius;
float target = b2Max(b2_linearSlop, totalRadius - 3.0f * b2_linearSlop);
float tolerance = 0.25f * b2_linearSlop;

Thanks.

Okay, so I take it that there’s no clear documentation of the offsets, although obviously they can be reverse engineered from the code. Although the docs hint at this by using the word contacts, they don’t make this distinction clear at all. Any casual reader of the docs would assume that a box cast would return the same distance as a ray cast, up to FP precision.

Is there a good reason why Unity doesn’t provide versions of these functions that do pure intersections? We all do the trick of using multiple ray casts to approximate a box cast, and it functions, but it’s pretty inefficient and silly, not to mention bug prone against unusual geometry.

The variable offsets depending on collider type makes compensating pretty impractical. I suppose I could do BoxCastNonAlloc to collect candidates, switch on the collider type, and reverse engineer compensation code or custom intersection code for each. But that’s silly.

@MelvMay as I’m sure you’re aware, this forum is filled with many posts over the years from people who find these offsets frustrating. They are understandable in the actual collision code, but are usually undesirable and problematic in pure queries, which may not even be being used for collisions at all.

Unity’s 3D physics does this correctly. The 2D does not. I understand that this happens because Unity uses Box2D and this is what Box2D does, but that’s an implementation detail, and not a good excuse for bad functionality in Physics2D.

I assume Unity maintains a fork of Box2D. You could implement versions of the queries that work without offsets and provide them as overloads. Please do.

This is also not properly documented. The only mention of this behaviour that I’ve found in the docs is in the defaultContactOffset page, and even that doesn’t actually explain the functionality w.r.t. different query and collider types. There’s nothing on any of the query methods. There are various threads over the years where you’ve posted saying that you’ll get the documentation fixed, but it’s never happened.