SimpleCharacterController as a platformer controller

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
Seleste
Posts: 6
Joined: Fri Apr 29, 2011 2:12 pm

SimpleCharacterController as a platformer controller

Post by Seleste »

I'm trying to use BEPU as the physics engine for a 2.5D platformer. I'm pleased as punch when it comes to simulating simple physics entities such as projectiles being thrown or boxes being flung around. Unfortunately, I'm having trouble fine-tuning the character controller. I'm using a slightly adapted version of the SimpleCharacterController, in a level defined by a StaticMesh.

My biggest hurdle at this time is that if the character is falling fast enough, it tends to penetrate into the level geometry. This penetration is slightly unsightly, which is bad enough, but the real problem is it can also slam the collision pair detector too far into level geometry, causing support raycasts to fail and the character to be stuck in "midair" while on the ground. I've read this thread describing rather similar issues, and have been trying multiple approaches to solving this particular problem, to no avail. In no particular order:
  1. Enlarging the collision pair detector (but not support height).
  2. Enabled CCD on both the body and the collision pair detector.
  3. Applied high stability settings as per the demos ConfigurationHelper.
  4. Separated space updating to its own thread.
  5. Limiting falling speed.
  6. Picking different values of support height.
The only attempts with positive results were limiting fall speed and raising support height. I was planning on limiting fall speed anyway, but the limit required to prevent penetration is slower than what I would've liked. As for support height, values high enough to avoid the penetration issues have the character floating above level geometry.

There's also an issue with the collision pair detector if the character jumps onto a ledge just narrowly enough that the ledge falls flat in the pair detector, then the body enters in collision preventing the character from going through the ledge, however the collision pair detector is too far deep and its raycasts fail to find a support. (I've uploaded a short clip illustrating that issue here: Fido the prototyping dog. What isn't shown is that once Fido is on the ledge, I'm constantly trying to jump, to no avail. As mentioned, code-wise, the raycast fails and no support is returned.)

There are a few things I've considered but haven't tried yet:
  1. Adapting CharacterController's implementation instead of SimpleCharacter's, meaning using the GJKToolbox.ConvexCast instead. But based on the thread linked earlier, the penetration problem would supposedly be worse with the CharacterController so I'm not too keen on going through that trouble to no avail if that's the case.
  2. Setting a big enough support height but offsetting the player model. However, this'd effectively set the controller body at a different place from the model, something I'm not too keen on.
From what I've seen, the SimpleCharacterController isn't quite cut out for precision platforming, so I'm not adverse to writing my own controller logic. However, before I go around trying that, I'd really appreciate some pointers on how I could mitigate and possibly completely prevent penetration for the character. All the solutions I could think of within the framework limits seem to be failing, although I admit to having very little hands-on experience with physics frameworks beyond naive, single-phase updating.

As an aside, how feasible would it be to make the PhysicsDrawer capable of updating its DisplayEntities when applying changes to their source Entities' sizing properties? I've been relying on a debug console to change things on-the-fly in order to test different results, for instance changing the support height. But even if I update the collision pair detector height after that, it doesn't actually change its display shape. A quick examination shows that the drawer only fetches shape geometry on initialisation. Being able to update display without recreating the collision pair box would be nice.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: SimpleCharacterController as a platformer controller

Post by Norbo »

Increasing the update rate will mitigate penetration, though it is costly. Try setting the Space.TimeStepSettings.TimeStepDuration to a lower value and passing the time delta into the space's update method. This will let it take as many steps as are necessary to keep up.

For the character controller support finding issue, it looks as though the character's support ray is below the triangle and the body is above the triangle. This can be addressed by starting the raycast from within the character's body entity, rather than at the very bottom.

A nastier failure can occur when the character's body is on the edge of a cliff, but the ray is off the cliff and casting into the void. Ensuring that the body of the character has zero friction with the ground in this case will let the body slide off gracefully. If you have a lot of 'air control' this issue won't be apparent.
Adapting CharacterController's implementation instead of SimpleCharacter's, meaning using the GJKToolbox.ConvexCast instead. But based on the thread linked earlier, the penetration problem would supposedly be worse with the CharacterController so I'm not too keen on going through that trouble to no avail if that's the case.
The non-simple CharacterController does not work for non-entity supports in v0.15.0 and beyond (it's still around for demonstration and testing purposes). I wouldn't recommend trying to hack it to a fully functioning state; a superior character controller is in the pipeline for v0.16.0.
As an aside, how feasible would it be to make the PhysicsDrawer capable of updating its DisplayEntities when applying changes to their source Entities' sizing properties? I've been relying on a debug console to change things on-the-fly in order to test different results, for instance changing the support height. But even if I update the collision pair detector height after that, it doesn't actually change its display shape. A quick examination shows that the drawer only fetches shape geometry on initialisation. Being able to update display without recreating the collision pair box would be nice.
The easiest way to get something like this is to remove and re-add the entity after its dimensions have changed. That will force a recalculation of geometry.
Seleste
Posts: 6
Joined: Fri Apr 29, 2011 2:12 pm

Re: SimpleCharacterController as a platformer controller

Post by Seleste »

Norbo wrote:Increasing the update rate will mitigate penetration, though it is costly. Try setting the Space.TimeStepSettings.TimeStepDuration to a lower value and passing the time delta into the space's update method. This will let it take as many steps as are necessary to keep up.
Ah, my bad, I'd misread this as TimeStepDuration being the value used for Update() and having no impact on Update(float). Either way, it does offer a noticeable improvement, although it's still possible to have it penetrate too far with unlucky timing. However, I now realise the penetration is only that noticeable because it gets the character stuck into the floor in air mode and unable to jump -- fixing the character support raycast as you suggest should make it a lot better.

On the other hand, I wouldn't want to make the whole world update more just to fix one object. I don't presume there's a way to make one object update more than the others? I imagine that could be unstable for collisions with moving objects if the latter didn't update at the same rate.
Norbo wrote:For the character controller support finding issue, it looks as though the character's support ray is below the triangle and the body is above the triangle. This can be addressed by starting the raycast from within the character's body entity, rather than at the very bottom.

A nastier failure can occur when the character's body is on the edge of a cliff, but the ray is off the cliff and casting into the void. Ensuring that the body of the character has zero friction with the ground in this case will let the body slide off gracefully. If you have a lot of 'air control' this issue won't be apparent.
Good call! As for that failure condition, I was planning on finding supports from two raycasts down from approximately the character's feet anyway. That way you have the good old platformer behaviour of being able to stay on a ledge as long as you're standing on one pixel. ;)
Norbo wrote:The non-simple CharacterController does not work for non-entity supports in v0.15.0 and beyond (it's still around for demonstration and testing purposes).
I'd read as much from the many topics on the CharacterController -- I just wasn't sure if it was something fixable or not worth the trouble. I'll wait on 0.16 to see the new controller, until then I'm in a bit of a rush for time so I'll keep on hacking at it until it becomes FrankenController. :shock:
Norbo wrote:The easiest way to get something like this is to remove and re-add the entity after its dimensions have changed. That will force a recalculation of geometry.
Ah, that makes sense. I did, however, run into a crash when removing a Box using the InstancedModelDrawer. The second copy in ModelDisplayObjectBatch.Remove has an array count mismatch causing this exception:
Destination array was not long enough. Check destIndex and length, and the array's lower bounds.
Stack Trace wrote: mscorlib.dll!System.Array.Copy(System.Array sourceArray, int sourceIndex, System.Array destinationArray, int destinationIndex, int length) + 0x13 bytes
> BEPUphysicsDrawer.dll!BEPUphysicsDrawer.Models.ModelDisplayObjectBatch.Remove(BEPUphysicsDrawer.Models.ModelDisplayObjectBase displayObject, BEPUphysicsDrawer.Models.InstancedModelDrawer drawer) Line 136 C#
BEPUphysicsDrawer.dll!BEPUphysicsDrawer.Models.InstancedModelDrawer.Remove(BEPUphysicsDrawer.Models.ModelDisplayObjectBase displayObject) Line 81 C#
BEPUphysicsDrawer.dll!BEPUphysicsDrawer.Models.ModelDrawer.Remove(object objectToRemove) Line 180 C#
I'm afraid I can't really debug this any further by myself, as I don't have the slightest idea how instanced drawing works besides the broad principle of it. Let me know if you could use more precise reproduction steps and I'll try to isolate a small project causing the crash.

The BruteModelDrawer doesn't have this crash, however after removing and readding the entities, they don't display anymore. Closer inspection reveals they aren't added anew, and the reason why is in ModelDrawer:

Code: Select all

        internal ModelDisplayObjectBase GetDisplayObject(object objectToDisplay)
        {
            Type displayType;
            if (!displayObjects.ContainsKey(objectToDisplay))
            {
                (...)
            }
            return null;
        }
The base ModelDrawer.Remove(object) doesn't actually remove the dictionary entry once done with the derived remove. Adding displayObjects.Remove(objectToRemove); on the next line fixes it.

For what it's worth, I'm using the stable source branch (I didn't even realise the develoment branch was separate until today!).
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: SimpleCharacterController as a platformer controller

Post by Norbo »

Thanks for the report; the instanced model drawer crash should now be fixed (along with the invisible objects) in the latest development build: http://bepuphysics.codeplex.com/SourceC ... evelopment
On the other hand, I wouldn't want to make the whole world update more just to fix one object. I don't presume there's a way to make one object update more than the others? I imagine that could be unstable for collisions with moving objects if the latter didn't update at the same rate.
There is no way to update a single object at a different rate at this time, for reasons similar to what you imagine.
Seleste
Posts: 6
Joined: Fri Apr 29, 2011 2:12 pm

Re: SimpleCharacterController as a platformer controller

Post by Seleste »

Firing a pair of raycasts from the middle sides of the body is proving to be much better for support stability, but I can still occasionally see penetration and it's rather jarring. So I've been thinking about a way to prevent it instead of reacting to it, I'd just like to throw it out here as a feasibility check.

First off, the only thing characters will consistently be in collision with is the level geometry. The great majority of physics items in the space will be decorative props loosely synchronised over the network. In order to prevent these from getting in the way, or causing imbalance if they turn out of sync and modify the behaviour of non-authoritative players, I'm just going to disable collisions between players and props. Entities that will allow collisions will be few and far between, and can get away with some penetration.

So in order to make level collisions more reliable, I was thinking to skip broad phase between players and the level and instead have them fire their two raycasts constantly. The maximum length of these raycasts would be relative to character velocity projected onto the Z axis, so as to make them long enough to include collisions from the next few time steps. Once such a collision is discovered, just adjust the target collision time each timestep until it would happen in the next step. Then, during that last step, use a few iterations of a root-finding algorithm (most likely Newton's, given the fact that the character's movement derivative should be simple to compute) to get the exact collision point, and place the character at that location (with some possible additional checks and adjustments if the ground is sloped) instead of simulating normally.

Now while I'm rather familiar with numerical analysis and theoretical concepts, I've never toyed around with a physics engine up until now. I'm mostly curious as to whether there are practical improvements to this approach without losing precision, or if BEPU provides some utilities for calculating any of those steps.

Edit: Actually, I think this can mostly be disregarded -- I'd made a mistake in my ray origins and they weren't actually firing from the middle left and middle right of the body. Fixing that and taking body half height in consideration for the raycast and support distances already yields much stabler results even at default space update rate. I've managed to cause penetration by manually applying ridiculous speeds, but within normal parameters I haven't seen it happen.

Come to think of it, by adding extra length to the raycasts, I'm basically doing exactly what I described, minus the numerical solving (which I'm sure happens to an extent behind the scenes anyway).
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: SimpleCharacterController as a platformer controller

Post by Norbo »

minus the numerical solving (which I'm sure happens to an extent behind the scenes anyway).
When entities are using the Continuous movement type, there is indeed some of that happening. It uses a form of conservative advancement to test core spheres against opposing geometry (which, when using motion clamping, tends to produce nicer simulation results than the whole shell). The core shape scaling which controls how large the sphere is relative to the minimum radius of an object can be found in MotionSettings. It's helpful for stopping an object from flying through a wall completely, but intentionally avoids stopping every time things might touch. This allows some penetration to occur, depending on the core shape scaling and shape type.
cjhazard
Posts: 35
Joined: Mon Aug 01, 2011 8:05 pm

Re: SimpleCharacterController as a platformer controller

Post by cjhazard »

Hope no-one minds if I resurrect this thread. I'm also trying to create a platform-style character controller, but I'm trying to achieve things a little differently.

Rather than raycasting I simply want to retrieve the contact normal of a collision between the player and a platform. I then use this to determine if the character is on the ground (made simpler because my player character will always be upright). So for example, if the dot product of the Contact.Normal and Vector3.Down > 0.8 then the player is on the ground. Seems to work ok except that the Contact.Normal doesn't always originate from the player (it may be the normal of the platform surface and therefore closer to Vector3.Up). Is there a way to always get the contact normal from the perspective of the player character?

Thanks.
cjhazard
Posts: 35
Joined: Mon Aug 01, 2011 8:05 pm

Re: SimpleCharacterController as a platformer controller

Post by cjhazard »

To make things a little clearer, here is the code I'm currently using...

Code: Select all

private bool CheckIsOnGround()
        {
            for (int i = 0; i < (_collisionMove.PhysicsEntity as Entity).CollisionInformation.Pairs.Count; i++) {
                CollidablePairHandler pair = (_collisionMove.PhysicsEntity as Entity).CollisionInformation.Pairs[i];
                //Determine which member of the collision pair is the possible support.
                Collidable candidate = (pair.BroadPhaseOverlap.EntryA == (_collisionMove.PhysicsEntity as Entity).CollisionInformation ? pair.BroadPhaseOverlap.EntryB : pair.BroadPhaseOverlap.EntryA) as Collidable;

                //Ensure that the candidate is a valid supporting entity.
                if (candidate.CollisionRules.Personal >= CollisionRule.NoSolver)
                    continue; //It is invalid!

                for (int j = 0; j < pair.Contacts.Count; j++) {
                    float dot = Vector3.Dot(pair.Contacts[j].Contact.Normal, Vector3.Down);
                    if (dot > 0.8f) {
                        return true;
                    }
                }
            }

            return false;
        }
Alternatively, if I can determine which surface the the contact normal belongs to then I can either 'dot' with Vector.Down or Vector.Up accordingly.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: SimpleCharacterController as a platformer controller

Post by Norbo »

As luck would have it, I've made a SphereCharacterController in the last week or so that you can grab in the development version: http://bepuphysics.codeplex.com/SourceC ... evelopment

The primary form of support in the SphereCharacterController is contacts, like the non-simple CharacterController. Ray casts are used as supplements to assist in going down steps without losing traction. They are only used when there are no contacts, so their usage is rare.

The end result is a character which has completely continuous motion. Walking up steps is the same as walking up a slope, unlike the CharacterController in which stepping involves discontinuous teleportation to emulate common FPS behavior.

But being a sphere, the SphereCharacterController doesn't support crouching.

In addition, since it is a sphere and only rarely uses any supplementary queries, it is quite cheap. On average, it is around 20-40% cheaper than the full CharacterController as tested with 1024 characters random-walking around the playground mesh. The CharacterController also has greater 'spikes' due to expensive full-shape queries needed to accommodate discontinuous stepping and other behaviors.

I wouldn't be surprised to find that the SphereCharacterController is actually faster than the SimpleCharacterController too, though I haven't bothered to test that yet.


All that said, to answer a question more directly:
Seems to work ok except that the Contact.Normal doesn't always originate from the player (it may be the normal of the platform surface and therefore closer to Vector3.Up). Is there a way to always get the contact normal from the perspective of the player character?
The normal can be pointing towards or away from one of the objects in a pair. In a collision, no object is 'special.' At one point, I did attempt to unify the simpler collision pairs' handling of collision normals. The normal points from object A to object B for convex-convex pairs and some others. However, this is unfortunately not a universal thing- the architecture of some multi-shape pairs like compounds and meshes violate the rule.

Instead, as you can see in the support finders of the CharacterController and SphereCharacterController, I calibrate the normals relative to the character by comparing the contact position to the character position. A dot product between the offset and normal can be used to ensure the normals are always pointing out or always pointing in relative to the character.
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: SimpleCharacterController as a platformer controller

Post by Norbo »

I should note to any later readers who might get confused by this thread:
The original "CharacterController" of pre-v0.15.0 times was replaced back in v0.16.2 with a fully functional and robust version. Have no fear in using it!
cjhazard
Posts: 35
Joined: Mon Aug 01, 2011 8:05 pm

Re: SimpleCharacterController as a platformer controller

Post by cjhazard »

Thanks Norbo, I'll check out the SphereCharacterController to see if I can figure it out.
Post Reply