We'll start with the questions, then a pile of background:
- How do you rigorously and thoroughly define "soft collision" of the Mario-esque type (if I start above, or in a jump my feet apex above a "soft platform", I'll land on it)?
- Can you suggest a definition in terms of object-versus-segment (or line, or ray, or whatever similar terrain boundary), in a SAT framework?
- Any good examples/references for soft collision out there, even in non-SAT frameworks?
- Finally, any edge/corner cases, literal or otherwise, that might not be obvious?
6+ years ago, I built a simple AABB object versus line-segment terrain collision system for $work, based heavily on N Tutorial A - Collision Detection and Response. (This goes against my advice to never write physics/collision for production code oneself, but we had strange requirements: no FPU, blazingly fast SRAM and bus, and a relatively slow CPU.)
Over the years, we've dealt with a number of (sometimes quite literal) "corner cases", including how we deal with fast-moving objects (currently very fine-grained multisampling), order of collision resolution, et cetera. This is the usual difficult stuff for naïve SAT, it would seem.
Recently another issue with our "soft collision" -- that is, platforms one can jump "up" through, but not fall "down" through (where "up" and "down" are relative to a unit normal off the line segment) -- has cropped up.
This has me wondering if I've "stated the problem" correctly vis a vis soft platforms.
As background, the bug in this case involved characters jumping with an arc such that their "head" just hit the bottom of the soft line segment. Our collision system does, roughly:
- early-miss if
movement.dot( segmentUnitNormal ) > 0// moving away from segment, for all segments (debatable whether this should be here, but probably largely irrelevant for this problem)
- normal three-axis SAT (x-axis, y-axis, and line-axis) with miss if any axis shows separation; compute proposed
projectionto displace box out of segment if hit
- late-miss unless
0 <= projection.dot( segmentUnitNormal ) < 1// for "soft" segments only
- finally, hit
Step #3 -- specifically the
0 == part of
0 <= -- is the issue here. (On the other end of the range, the choice of
1 as cutoff for maximum interpenetration is arbitrary: it's based on our multisample worst case projection response.)
Amusingly, the comment above step #3 in the code says something like "Miss unless we started 'above' the segment, or if interpenetration is too deep."
The logic as stated says nothing about starting above, instead dealing only with response along unit normal.
As mentioned, the
0 case here is the issue: if the character's "head" hits the segment exactly at the peak of his jump and we compute a zero response, we'll treat that as a hit, when we'd expect to only get hits when our "feet" are at or just above the segment.
We could just exclude the 0 case here, and deal with a little "bounce" when resting on our soft platform, but then we'd have strange cases where we might not seem to be in contact with the world geometry every other frame, which has other implications for the animation system and the like. I'd like to preserve 0 interpenetration = hit behavior.
I have a few possible solutions, mostly boiling down to things like:
- "check if AABB centerpoint started on the correct side of the segment", or
- "retain the directionality of the projection even if it collapses to zero length" (something like a "signed zero" here -- we already have this from our SAT calculations earlier),
...coupled with a little more care re whether we're using "computed projection along the line axis" versus "shortest computed projection" in these tests.
All of these solutions seem to be robust, in my initial tests.
But it has me wondering if I'm missing something obvious; hence the questions above.