Assuming this kind of behavior is what you're looking for:

- surfacefollowing.jpg (12.57 KiB) Viewed 4224 times
then this is quite similar to a 6DOF-walk-on-walls SphereCharacterController variant I built a while ago (which unfortunately isn't public). In that project, I found that using an enlarged spherical collision query to find potential supports and former supports worked the best.
The process looks like this:
1) Collect any nearby contacts. You can do this using a one-shot query like the SphereCharacterController.QueryManager.QueryContacts function, or you can just keep a sphere in the space to collect pairs over time. If the sphere actually belongs to the space, you'll probably want to set the collision rules to disable collision response.
2) Calibrate the normals to point at the center of the sphere. Collision detection will produce contacts which can face either towards or away, since neither object is 'preferred'. Do this by negating normals which point 'away' from the center (or vice versa). You can measure which way they face by comparing the offset from the center to the contact position to the contact normal. To avoid corrupting any internal state, I would recommend doing the contact modifications on your own game logic copies of the contact data.
3) Update the 'support'.
-If the object does not yet have a support, check the list of queried contacts. If one exists, consider it the support. If multiple exist, use a heuristic to choose which one will be the support.
-If the object already has a support and there other options available, use a heuristic to determine if it should switch to a different support.
-Generally, if there exists a contact which has a similar normal to the previous support, it's a good idea to use it as the new support. This makes it easy to smoothly traverse curved surfaces or go over convex edges.
-Examples of other heuristics: is the object moving toward or away from this contact? Can use either velocity or the desired goal velocity for this purpose. If the object's running into a wall, the support probably needs to swap to the wall contact.
-When the support is updated, use the shortest rotation between the former support normal and the new support normal to update any orientation stuff (goal movement direction, object orientation, or whatever else).
4) While the object has a support, remove velocity along the support normal. Include an extra term to keep the object at a certain distance within the sphere's radius to avoid drifting off. In pseudocode:
Code: Select all
object.Velocity += supportNormal * (-relativeVelocityAlongNormal + distanceCorrectionTerm)
Which expands to:
Code: Select all
object.Velocity += supportNormal * (-Vector3.Dot(object.Velocity - supportVelocity), supportNormal) + (goalDistanceFromCenterToSupport - currentDistanceFromCenterToSupport) * 0.2 / dt)
The distance correction term's multiplier of 0.2 / dt tries to remove 20% of the error per frame.
(Small sidenote: the distance as measured from the sphere center to the contact position isn't exactly the distance from the center of the sphere to the nearest surface. The contact tends to be somewhere in the middle of the intersection volume. If you wanted to, you could find an approximation of the surface by taking into account the contact penetration depth or casting a ray from the center of the sphere to the contact position.)
Here's how the query sphere would look as it went around a convex edge, along with an approximate visualization of the support:

- surfacefollowingqueries.jpg (27.88 KiB) Viewed 4224 times
Collision queries have the advantage of not caring where the contacts came from. Whether it's moving on meshes, boxes in a static group, or just entities directly, it just works.