What exactly is the "capsule on a stick"? Is that using a custom velocity simulation (outside of the physics engine) and doing a convex sweep to check how far it can go along that direction?
"Capsule on a stick" refers to a very simple form of character where the character's collision is represented by a capsule or similarly simple shape and it detects supports using a ray cast pointing straight down out of the bottom of the shape. The normal and location provided by the ray cast informs a velocity calculation to control the character's motion.
Usually there will be some form of relatively stiff spring-ish calculation to maintain a target vertical offset above the ground. Since there's a gap between the capsule shape and the support, this automatically supports a form of upstepping with no additional complexity- if the character moves over a higher surface, the vertical spring-thing will naturally push it up. Likewise, when a character walks off an edge, the ray can extend down to detect potential supports and pull the character down (if you want that behavior).
In games where the level design is suitable, this kind of character can work great. It can run into some issues with arbitrary geometry, though- the above design would happily attempt to step up into a space which is too small to stand up in, for example. And what happens if the character walks over a higher surface that is too steep to support traction? These issues all have sorta-solutions, but some of them can be gameplay specific or just pretty hacky.
I'm trying to understand what exactly does the character controller in v1 do and how much of it is needed for a simple approach.
The v1 controller can be broken into a few different chunks:
1) Support detection
2) Velocity control
3) Upstepping/downstepping
4) Stance changing
Going back through them in detail:
1) Support detection is based on the contacts generated with the character's collision shape. In v1, this was typically a cylinder with a large margin- halfway between a cylinder and a capsule. Any contacts with the environment with normals pointing up were classified as 'supports', and then a subset of those with sufficiently gentle slopes were considered 'traction supports'. When the character has traction, it enables full motion control. If supported but without traction, a more limited form of control is used.
The character also picks the most 'important' supporting object (based on some heuristics) to act as the representative for the purposes of applying forces. That is, if the character has contacts with multiple dynamic bodies, the character will try to pick the one that is more likely to be carrying the character's weight. It'll be pushed around as the character tries to move if it's dynamic.
Using contacts for this purpose is pretty simple since the engine's already done all the work, you just have to check for its results and reinterpret them.
2) Velocity control is the process of applying impulses to the character (and its dynamic support if it exists) to make it move in the desired way. In v1's character controller, this is accomplished by a pair of constraints, the HorizontalMotionConstraint and VerticalMotionConstraint. Conceptually, they're pretty simple- the horizontal motion constraint is just a solver-compatible implementation of 'try to reach a target velocity in the target movement direction', which you could implement in non-constraint form if you wanted. The VerticalMotionConstraint's job is also simple- it just tries to eliminate motion that would separate the character from the support surface (up to a limit). It works kind of like magnetized boots- not exactly physically correct in most cases, but in gamelike use cases you often are dealing with velocities that would launch your character into the sky if it didn't exist.
The only reason these are implemented as constraints rather than external velocity controls is that constraints have a more robust interaction with other constraints in the simulation. Pushing around dynamic bodies, for example, works better when the motion is controlled by a constraint rather than periodic external impulses.
3) Upstepping/downstepping involves a bunch of complex queries to identify alternative positions for the character. If the character has 'side' contacts (normals that are not pointing up or down relative to the character), it can check to see if there is space for the character to stand on top of the obstacle. If there is, it can teleport up.
Downstepping is similar- If the previous frame had traction and the current frame doesn't, it tries some positions down from the character's current position to see if it can regain traction.
About 80% of the character's total complexity is dedicated to getting stepping robust, and it can really hurt performance in pathological cases.
4) Stance changing is just changing the size of the character's shape in response to a desired state like standing or crouching. This is pretty simple, but there is one difficulty- going from a smaller shape to a larger shape requires queries to guarantee that the larger shape actually fits. This is like a mild version of upstepping and can be somewhat expensive. Notably, that effort is only required because the stance shift is instantaneous- if the stance changed smoothly, you could perform the tests incrementally.
Now, what parts are required for a simple version of v1's controller: just #1 and #2, and #2 could be simplified further by just using external velocity controls rather than constraints.
#3 can be handled implicitly by making the character a capsule and just using its rounded bottom plus the contact-based support finding to slide up/down obstacles rather than discontinuously teleporting up them. It can be completely disabled by commenting out the query in the CharacterController:
https://github.com/bepu/bepuphysics1/bl ... er.cs#L611
#4 is sort of an optional feature and you could do query guards like v1's controller does, or you could do the smoother transition with incremental tests. You can also just ignore it and never change stance.
I got one more question: How well in general will the character controller perform against instanced mesh (semi-complex or even more complex)? Convex sweeps against these are generally quite slow, but raycasts (with some extra optimizations) are significantly faster.
Character controllers like the one in v1 rely on existing contacts rather than performing explicit queries (except in the case of stepping), so in the average case it's very fast. If you have an extremely complex environment where most characters are constantly performing some form of step query, that can get more expensive. Check out the CharacterStressTestDemo or CharacterStressierTestDemo in the BEPUphysicsDemos for examples of v1's full featured character performance- you can probably use 500-3000 in v1 depending on the target hardware, environmental complexity, and frame budget. (A contact-support based character in v2 without stepping would likely be 5-15x faster than v1, like everything else.)
Convex sweeps are indeed not superfast. A character that uses multiple sweeps every frame would probably end up an order of magnitude slower. Ray casts are faster, but still slower than not doing anything extra.
So I'm thinking about writing my own solution using the combination of the convex sweep against general colliders + raycasts against mesh ones, but I'm not entirely sure how much to take from the character controller in v1 for that so I don't have to reinvent the wheel.
I would hesitate to try to build something on top of sweeps and ray casts beyond something simple like the 'capsule on a stick' above- in general, if you can stick to contact-based support, it'll end up faster than more robust in the general case. That's not to say that it can't be done, but stuff can get complicated fast as you run into corner cases.
I've updated my plans for v2 a bit since my previous post a bit. In the next 3 weeks or so, I plan to create a character controller that is similar to v1's in terms of support finding and motion control, but without any discontinuous stepping or stance changing support. It'll just be a capsule with constraint-based motion. It'll be implemented outside of the library itself as a demo showing how to create custom constraint types as a side benefit. It wont have nearly as many features, but it's expected that users can take the relatively simple starting point and modify it as needed.