2
\$\begingroup\$

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?

The background:

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:

  1. 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)
  2. normal three-axis SAT (x-axis, y-axis, and line-axis) with miss if any axis shows separation; compute proposed projection to displace box out of segment if hit
  3. late-miss unless 0 <= projection.dot( segmentUnitNormal ) < 1 // for "soft" segments only
  4. 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.

\$\endgroup\$
4
\$\begingroup\$

Generally this is done with collision filters.

Set a filter on the block. This filter is called when a new collision is detected and tells the physics engine whether it should resolve the collision as usual or ignore the collision for the lifetime on the collision (meaning the filter's decision is applied until the objects stop colliding, and is called again next time the objects collide).

This filter then simply sets the collision as ignored if the initial collision is not a "downward" collision (that is, if the bottom of the moving sprite was above the top of the platform the previous frame). That's all there is to it.

Some games which allows players to "drop" through a platform would restart the collision when the drop key combination is pressed and would ignore any collision with such a platform so long as the key combination is pressed.

There is absolutely no need to change the collision detection (SAT or such). It's the resolution that needs to be updated to enable the use of filters and ignoring contacts per the filter's result.

\$\endgroup\$
  • \$\begingroup\$ Yeah, had no intention of changing the SAT part; there are just a lot of subtleties to the resolution/projection. There are also interesting things to deal with with sloped vectors, but I think I can get away with "center of AABB started on correct side of line". The stateful filter idea is useful, thank you -- but I may not use it, I think I'd prefer to come up with a stateless solution that doesn't involve carrying around a list or an extra bit of information per object-segment pair, even if it's more work. Anyway, +1... \$\endgroup\$ – leander Sep 16 '13 at 14:16
  • 1
    \$\begingroup\$ You want the stateful information. You can't do high-quality resolution without a "contact cache." There is a bit of a different between when two objects collide and when contact has simply been maintained. You can use a fixed-size table of object ids (the pair of ids, lower first, used as your key) to simply look up objects which were in contact last frame. This both helps maintain higher quality contact points and lets you have game logic events for "these objects collided" and "these objects separated" without firing off useless events every frame when two objects resting on each other. \$\endgroup\$ – Sean Middleditch Sep 16 '13 at 16:57
  • \$\begingroup\$ Fair enough, I can see the benefits; I've worked with both Box2D and Bullet and they both appear to have something along these lines. I'm still not 100% sure my definition of "above" is suitable for sloped lines, which probably would have been a smaller and more focused question to ask... \$\endgroup\$ – leander Sep 16 '13 at 18:03
  • 1
    \$\begingroup\$ You can use normals and something akin to SAT. Take the line's normal. Make sure on the previous frame the separation along that normal was >0. Since the objects are colliding, all separations are now <=0. A separation along that axis in particular means the object was on the "colliding" side of the line/object before and now isn't, so it moved into the colliding side of the line. \$\endgroup\$ – Sean Middleditch Sep 16 '13 at 18:11
  • \$\begingroup\$ Yep, that's exactly what I'm doing at the moment. =) \$\endgroup\$ – leander Sep 16 '13 at 18:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.