BetterCharacterControl bug and possible fix (+some stuff)

Hello everybody again.

First, normen you did a wonderfull job, i really mean it.

But (hey, but “but”, sorry) I think that there is a bug in the BetterCharacterControl.

When I set the gravity to (0, 0, 10) the character direction is updated like it should but I am not falling in the right direction. Note that if I replace this by (0, 0.001f, 1) everything work like a charm.

I found that this bug is made from this part of code (method : prePhysicsTick ):

[java]
//dampen existing x/z forces
float existingLeftVelocity = velocity.dot(localLeft);
float existingForwardVelocity = velocity.dot(localForward);
Vector3f counter = vars.vect1;
existingLeftVelocity = existingLeftVelocity * physicsDamping;
existingForwardVelocity = existingForwardVelocity * physicsDamping;
counter.set(-existingLeftVelocity, 0, -existingForwardVelocity);
localForwardRotation.multLocal(counter);
velocity.addLocal(counter);
[/java]

But I can’t say why (my maths are too far behind me). What I mean is: when I comment this code I fall in the right direction (but, then, there is no more dampen for x/z direction).

So, I put this code in comments (I mean: added /* and */) and wrote this:

[java]
/* get the current gravity */
Vector3f gravity;
gravity = vars.vect1;
rigidBody.getGravity(gravity);
gravity.normalizeLocal();

/* keep only the vertical part of the velocity */
velocity.projectLocal(gravity);
[/java]

I don’t know if it’s “good” in general, but it works for me: I can change the direction and everything works just fine.
I don’t know if it has a side effect that would lead to a bug in certain case.

Also, I think that something is not a really a bug but should be documented.
If I execute this code:

[java]

public static void main(String args[])
{
BetterCharacterControl bcc;
bcc = new BetterCharacterControl(1, 2, 79);

Vector3f initialGravity;
initialGravity = new Vector3f(0, -10, 0);

bcc.setGravity(initialGravity);
bcc.getGravity(initialGravity);

System.out.println(initialGravity);
System.exit(1);

}
[/java]

It will display: “(0.0, -790.0, 0.0)”

Why is this, for me, a source of bugs? Well, I put gravity to (0. -10, 0) but when I take it back I get the gravity multiplied by the weight of the character control.
So, if for example I write:

bcc.getGravity(storage);
storage.negateLocal();
bcc.setGravity(storage);

i am NOT setting the gravity to its opposite, i am setting the gravity to its opposite MULTIPLIED by the weight.

Add to this that there is no method to get the weight of the character control and you get that there is no way (I don’t consider reflection) to get the actual gravity of the character.
This bug is NOT from your implementation (as I said, you did a good job), as you only use the PhysicsRigidBody’s getGravity method. But maybe add a line in the javadoc to describe that comportment would be nice.

Also, add a method that returns the weight of the character (it’s already a field, it’s not hard to do) or a method like “getSettedGravity” which give the … well, gravity which has been set previously, even if I know that “setted” is not an existing word ;).

For the record: I also had some case where suddenly I get an “hyperspeed” when I change gravity (like a big level disappear from my sight in less than 1 sec). I wasn’t able to reproduce that.
I don’t have it since I use my method to dampen x/z speed, but it may be just luck (or bad luck).

For the record too: I think it could be nice to add a “pivot” to the BetterCharacterControl, like a point that doesn’t move when we change gravity. By default, there is nothing which is equivalent to a pivot at the base of the character. I added a piece of code that warps the character when the gravity changes to make the eyes the “fix point”. I can share the code if someone wants it, but my fixpoint is a spatial (it should be a vector3f but then I would have to recalculate its new position myself and my maths are too bad for this, so I think that someone else could do it).

2 Likes

Change the direction gradually, then you should have no issues.

It’s not a possibility in my game as the gravity change from some event (it’s not to walk on the surface of a planete, it’s more a puzzle game).

Just set the gravity direction twice in a row, in one method, but with gradually changing direction, like:
(Gravity = 0,0,1)
setGravity(0,1,1)
setGravity(0,1,0)

The problem with your implementation is that theres actually no movement in the x/y plane where it should actually just be dampened. The problem with “sudden” changes in the gravity direction is the extreme points in the quaternion rotation.

The “pivot” should actually be at the bottom of the cylinder. If it doesn’t revolve around that I’d not know what to do as its “just” the bullet gravity direction and the collision shape has its center there.

Ok, after some try i managed to do this code:

[java]
// vars is a TempVars

Vector3f normalizedGravity = vars.vect1;
normalizedGravity.set(gravity);
normalizedGravity.normalizeLocal();

/* we want to compare the gravity and the localForward, but if we just use "compare",
 * maths approximations will hide some case where both are equals. So, we consider that
 * they are equals when the angle between them is small enough. 
 */

float angle = normalizedGravity.angleBetween(localForward);

if ( angle < FastMath.FLT_EPSILON )
{
  Vector3f precGravity;
  precGravity = vars.vect2;
  rigidBody.getGravity(precGravity);
  precGravity.normalizeLocal();
  
  Vector3f middleAngle = vars.vect3;
  middleAngle.interpolate(normalizedGravity, precGravity, 0.5f);
  super.setGravity(middleAngle);
}

[/java]
(the “super” is because I am subclassing bettercharactercontrol)

To make a long story short : if the new gravity is not “good” i make 2 steps, the first step being the interpolation between the old gravity and the new one. However:

  1. if the 2 gravity (the old one and the new one) are opposite, the interpolation will change nothing (the “step” will be either the old gravity or the new one or a (0, 0, 0) gravity).
  2. I didn’t encounter the case with the gravity bug when I do this interpolation BUT I encountered the “swiping” case (where my character seems to walk with soaps as shoes). Once again, I don’t know where it comes from. When I set the gravity to (0, 0, 0) (for the transitional gravity) I also get the swiping but the gravity bug occurs in some other conditions. Just gives me headache.

Also, I noted that, when I am falling, if I walk right to a wall (even with no gravity change) my character seems to “stuck” on the wall. I stop falling and, as long as I keep walking to the wall, I can go to the left and to the right (giving a nice but unwanted “wall walking” effect ^^). And I can’t jump while I do that. This is not related to our precedent bug, however.
This can easily be removed be disabling the air control (btw, it could be nice to add an “air control factor” (with set and get) as a float and use the “onGround” to modify the influence of the walkdirection (by multiplying it by the float value) – the air control may not be a good thing in some game).
However, remove the air control is not a solution here. Ray test if we are against a wall is not an option (if only a part of the body is against a wall the bug appears).
The ccdmotionthreshold of the rigidbody doesn’t seem to have an influence.

I don’t know if it’s relevant… but in Mythruna’s physics engine I had this problem when player → wall contacts had friction. The force pushing into the wall was equivalent to the force pushing back so friction ruled the day. Bullet is certainly more elegant than my engine so may not have this issue… but I generally find some issues to be extremely common.

Ok, sorry i wasn’t working on my code for several days as i had to help someone. Now I have more time and even if I didn’t code, I thought a lot about it.

First of all, I didn’t test but I bet that the duck part of the bettercharactercontrol has a bug. If it only performs a ray test to check if it can unduck, then there will always be a case where the ray will miss the roof. I made a small picture on paint to show an example in 2D (but you can imagine it in 3D).
About the dampening of the x/z velocity, I realized that it also means that I’ll not be able to make an explosion that will push back the player. I wonder why we dampen this velocity while we are in the air anyway. We should only take care that the velocity doesn’t increase due to the walkdirection’s stuff (as the behaviour will be unrealistic, making the character go faster and faster) but we shouldn’t dampen the velocity while the character is in the air, or I don’t understand why we use the rigidbody in the first place. If it’s just to apply force to other objects, we can just detect collision with the kinematic and apply forces by ourselves.
About the “stuck on wall”, I thought of some ways to deal with it, and I arrived to the conclusion that the best way would be to store the current collision. In this way, we can have a comportment “by default” (i.e. we transform the walkdirection into a planar vector, where the plan is the one which is perpendicular with the normal of the collision. It’s the plan of the wall), but we can also code “nice” comportments like wall jump or a custom “wall run”. The comportment could be in a sub-classable method, letting the user code its own special comportment that fits to his game, but giving a “standard comportment” (i.e. falling).

I think we can keep a trace of the collisions just like the ghost control do. It should be something like : add the geometry to the collection when a collision occurs (so the charactercontrol need to be a physicscollisionlistener) and clear the collection every prePhysicsTick.

By the way, about the duck/unduck stuff, it could be a solution to use a ghost control to “check” if a collision will occur when we unduck. I think that it’s not really possible to know if a geometry will collide with an other one with only one ray test and, anyway, a physic engine is made for this kind of stuff, we should let it optimize it (and possibly use the PPU).

I think I can provide an implementation which will solve the problem with the wall, if you think that the way I want to go is not a dead end (i.e. store a collection of geometry which are colliding with the character).

Also, I learnt about an implementation of a charactercontrol which use a sphere and a cylinder. The cylinder has the constraint to always be vertical (and don’t turn when hit by something) and the ball is used to simulate the foot stuff. To make the character walk you have to add an angular speed to the ball and nullify it when the character is standing still. The ball has an infinite friction if I remember well.
The ball and the cylinder connect with joints and this also give an “easy” way to dampen irregularity of the ground (you can have a smooth recover for the joints, so if the ground is full of little emboss the camera won’t shake).
The “footstep height” (i.e. the height the character control can walk over without bumping on it) is given by the radius of the sphere.
Another advantage of this is that it’s really easy to change the gravity as you only need to turn the cylinder around the ball.

NOTE : I wrote the precedent part of this message days ago. Since that, I tried several things and here is my conclusions :

  1. the bettercharactercontrol doesn’t have a stepHeight, meaning that if you walk to stairs you’ll hit them and you won’t climb the stairs. Also, this means that if you have small irregularities on the ground you’ll start to “fly” all the time. I made a something based on voxels and it appears that due to multiplications with float my faces are not strictly aligned, making the ground a LITTLE irregular. But this is enough, and when I walk I just bounce (like if I was jumping) all the time.
    Note that there is a fix to that, but it could lead to some other bugs. The solution is to apply continuously a force under the character control to make it behave like an hovercraft. This will simulate a stepheight in a nice way (for going upstairs) and you can even revert the force (for going downstairs. If you revert the direction of the force you will go down faster).
    Here is a picture that represents this:

For the record, I already implemented this and it works. However, the obvious consequence of that is that there is no collision when the character is standing (or walking or whatever) on something, because he is hovering over it. I think that there is a way to trigger the collision ourselves.

  1. For the bad news : when you walk to a wall with the bettercharactercontrol if you go fast enough you’ll just go through it, no matter which value you put in the ccd. This is because we don’t convert the direction of the character control when it hits a wall. So, instead of sliding along the wall, we keep its speed to a constant value, and he goes into the wall like a bull with a red wall. Problem: for a reason I didn’t figure out, this constant speed seems to bug the physics engine and especially the collision recovery part. The old charactercontrol class solved this by doing manually some “collision search and recovery” (the part with the mention to the quake engine). I have no solution for that, except import the solution from the old charactercontrol. I tried to use the collision listener to solve the problem but I got some strange bugs with it, like a getNormalWorldOnB which was not a normal of the geometry. However, I think that it’s because I am really bad at maths (and especially geometry).
  2. I tried to create some character control, including the one with the ball, and it’s end that even when I specify a 0 restitution everywhere I keep bouncing a little. Plus, even if the “step height” that this solution provides allowed me to climb stairs, I was still bouncing on irregular grounds, and I wasn’t “following the ground” when I went downstairs. So, I think that this is not a good solution after all.

Ok, here is a first version of the HovercraftCharacterControl. This is an ugly code and a lot of things need to be done. For example, I don’t know if it’s framerate independent or not (I don’t exactly know what make a physicscharacter framerate dependent).

This is a (poor) implementation of what I said before, with a hovercraft part and a “magnet” part. Every physics tick I detect collisions under the character control (with a ray for now, but I’ll try to update this to use a real collision detection) and if the ground is too close I push it(the character control) up and if the ground is a bit far (not too far) I pull the character control down.

This would be easier to implement if there was some “magnet” and “reactor” component in bullet, and I think that it could be a nice thing to add (to have vehicles like in the game “killer loop” for example :wink: ). It would also be a way to avoid vehicles flip (just magnet the wheel or even the chassis).

My main problem was to find a function that goes from [0, 1] to [0, 1] with a very harsh changement, like x^200. Why ? Because I wanted a fast recovery for almost every “recovery height” but still a smooth and small recovery for the end. To be clear, I detect the collision and according to the “hit fraction” (the distance from the rigidbody where the collision occurred, 0 being near and 1 being far) I apply a recovery with a very strong value for [1, 0.0001] and a really weaker recovery for ]0.0001, 0]. This is the idea : fast most of time, smooth at the end.
However, I didn’t find a good function like that and I made some terrible code.

I tested this with a stepheight of 1.1 (this is a very high value, it means that you can walk and you won’t care for obstacles with a height of 1m10 cm or less). It works, even if the vertical recovery is not high enough (you cannot walk straight forward without losing speed).

I think that the final implementation should take care of the walk direction, so it will be able to apply a vertical recovery speed that allow the character control to walk and “fly” above the objects. If the character is slow, the recovery will be slow etc.

I had some problems with the jump and the “magnet” and I made a quick fix that will work in some case. I need to rework on this too.

Also, I think that use a Vector3f for the walkDirection is not a good idea.
First, we are here talking about walk, not fly. This should be a Vectof2f, planar in 3d to the plan normal with the gravity. Second, I think that there should be 2 different things, the walk speed and the walk direction. Both should be a float but the direction should be an angle. I didn’t implement this yet and before I made this I would like to know what you think about it.

Also, I need to trigger the collision when I am “hovering” over the ground. I don’t know how to do this.
End, the last but not the least, i need to add a “slope” checking, meaning that i’ll keep only collisions that occured in a certain angle under the charactercontrol. I think i’ll do an other picture to explain that. And i’ll end this by doing an article to sumerize all of this.

To instanciate a hovercharactercontrol you need to do something like that:

[java]
new HovercraftCharacterControl(0.5f, 1.8f, 79, 1.1f);
[/java]
Where 0.5f if the radius of the character control, 1.8f its height, 79 its weight (mass … I know, physicly speaking it’s not true ^^ ) and 1.1f its stepHeight – even if you should lower this value.

I didn’t try it for normal environments as the only thing I have is a cube world.

[java]
/**
*

  • @author Bubuche
    */
    public class HovercraftCharacterControl
    extends AbstractPhysicsControl
    implements PhysicsTickListener
    {

protected final Vector3f scale = new Vector3f(1, 1, 1);
protected final Vector3f walkDirection = new Vector3f(1, 1, 1);
protected final Vector3f localUp = new Vector3f(0, 1, 0);
protected final Vector3f localForward = new Vector3f(0, 0, 1);
protected final Quaternion localForwardRotation = new Quaternion(Quaternion.DIRECTION_Z);
protected final Vector3f rotatedViewDirection = new Vector3f(0, 0, 1);
protected final Quaternion rotation = new Quaternion(Quaternion.DIRECTION_Z);
protected final Vector3f viewDirection = new Vector3f(0, 0, 1);
protected static final Logger logger = Logger.getLogger(HovercraftCharacterControl.class.getName());
protected float walkAngle;
private float stepHeight;
private float jumpHeight;
private List<PhysicsRayTestResult> bottomNear;
private PhysicsRigidBody rigidBody;
private float radius;
private float height;
private float mass;
private boolean onGround;
private boolean jump;
private boolean isInJump;

public HovercraftCharacterControl(float radius, float height, float mass, float stepHeight)
{
this.stepHeight = stepHeight;
if (stepHeight > height)
{
throw new IllegalArgumentException(“stepHeight(=” + stepHeight + “) > height(=” + height + “)”);
}

this.stepHeight = stepHeight;
this.jumpHeight = 1;
this.bottomNear = new ArrayList&lt;&gt;();

this.radius = radius;
this.height = height;
this.mass = mass;

rigidBody = new PhysicsRigidBody(getShape(), mass);
rigidBody.setAngularFactor(0);
rigidBody.setAngularDamping(Float.POSITIVE_INFINITY);
rigidBody.setFriction(0);

}

public void jump()
{
this.jump = true;
this.isInJump = true;
}

public void setJumpHeight(float jumpHeight)
{
this.jumpHeight = jumpHeight;
}

public float jumpHeight()
{
return this.jumpHeight;
}

public boolean isOnGround()
{
return this.onGround;
}

public void setWalkDirection(Vector3f walkDirection)
{
this.walkDirection.set(walkDirection);
}

public void setGravity(Vector3f gravity)
{
rigidBody.setGravity(gravity);
localUp.set(gravity).normalizeLocal().negateLocal();
updateLocalCoordinateSystem();

}

/**

  • Gets a new collision shape based on the current scale parameter. The
  • created collisionshape is a capsule collision shape that is attached to a
  • compound collision shape with an offset to set the object center at the
  • bottom of the capsule.
  • MADE BY NORMEN !
  • @return
    */
    protected CollisionShape getShape()
    {
    //TODO: cleanup size mess…
    CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(getFinalRadius(), (getFinalHeight() - (2 * getFinalRadius())));
    CompoundCollisionShape compoundCollisionShape = new CompoundCollisionShape();
    Vector3f addLocation = new Vector3f(0, (getFinalHeight() / 2.0f), 0);
    compoundCollisionShape.addChildShape(capsuleCollisionShape, addLocation);
    return compoundCollisionShape;
    }

/**

  • Updates the local coordinate system from the localForward and localUp
  • vectors, adapts localForward, sets localForwardRotation quaternion to local
  • z-forward rotation.
  • MADE BY NORMEN !

*/
protected void updateLocalCoordinateSystem()
{
//gravity vector has possibly changed, calculate new world forward (UNIT_Z)
calculateNewForward(localForwardRotation, localForward, localUp);
rigidBody.setPhysicsRotation(localForwardRotation);
updateLocalViewDirection();
}

/**

  • Updates the local x/z-flattened view direction and the corresponding
  • rotation quaternion for the spatial.
  • MADE BY NORMEN !

*/
protected void updateLocalViewDirection()
{
//update local rotation quaternion to use for view rotation
localForwardRotation.multLocal(rotatedViewDirection.set(viewDirection));
calculateNewForward(rotation, rotatedViewDirection, localUp);
}

/**

  • Gets the scaled height.
  • @return
    */
    protected float getFinalHeight()
    {
    return height * scale.getY();
    }

/**

  • Gets the scaled radius.
  • @return
    */
    protected float getFinalRadius()
    {
    return radius * scale.getZ();
    }

protected void applyVerticalRecovery(float tpf)
{
float cylinderHalfHeight = (height - stepHeight) / 2;

TempVars vars;
vars = TempVars.get();

final Vector3f position = vars.vect1;
final Vector3f limit = vars.vect2;
final Vector3f upPush = vars.vect3;
final Vector3f gravity = vars.vect5;
final Vector3f cylinderBottom = vars.vect6;




rigidBody.getPhysicsLocation(position);

rigidBody.getGravity(gravity);
float gravityLength = gravity.length();
gravity.normalizeLocal();

cylinderBottom.set(gravity);
cylinderBottom.multLocal(cylinderHalfHeight);

//position.addLocal(cylinderBottom);

/* check if on ground */
limit.set(gravity);
limit.multLocal(stepHeight);
limit.addLocal(position);
bottomNear.clear();
space.rayTest(position, limit, bottomNear);
onGround = !bottomNear.isEmpty();


if (!onGround)
{
  vars.release();
  return;
}


PhysicsRayTestResult result = bottomNear.get(bottomNear.size() - 1);
float fraction = result.getHitFraction();
float usedFraction = fraction;

rigidBody.getGravity(upPush);
upPush.negateLocal();
upPush.normalizeLocal();

fraction = (float) Math.pow(fraction, 100);
//fraction = (float) Math.pow(2, func * func * func);
//fraction = fraction - 1;
if ( fraction &lt; 0.0001f )
{
  fraction = 0.0001f;
}
upPush.multLocal(gravityLength+ (1/fraction) );


gravity.negateLocal();
rigidBody.applyCentralForce(upPush);


vars.release();

}

protected void applyVerticalRecoveryAndMagnet(float tpf)
{
float cylinderHalfHeight = (height - stepHeight) / 2;

TempVars vars;
vars = TempVars.get();

final Vector3f position = vars.vect1;
final Vector3f limit = vars.vect2;
final Vector3f upPush = vars.vect3;
final Vector3f gravity = vars.vect5;
final Vector3f cylinderBottom = vars.vect6;




rigidBody.getPhysicsLocation(position);

rigidBody.getGravity(gravity);
float gravityLength = gravity.length();
gravity.normalizeLocal();

cylinderBottom.set(gravity);
cylinderBottom.multLocal(cylinderHalfHeight);

//position.addLocal(cylinderBottom);

/* check if on ground */
limit.set(gravity);
limit.multLocal(stepHeight * 2);
limit.addLocal(position);
bottomNear.clear();
space.rayTest(position, limit, bottomNear);
PhysicsRayTestResult resultNear = null;
PhysicsRayTestResult resultFar = null;

for ( PhysicsRayTestResult r : bottomNear )
{
  if ( r.getHitFraction() &lt;= 0.5f )
  {
    if ( resultNear != null )
    {
      if ( resultNear.getHitFraction() &lt; r.getHitFraction() )
      {
        continue;
      }
    }
    resultNear = r;
  }
  else
  {
    if ( resultFar != null )
    {
      if ( resultFar.getHitFraction() &gt; r.getHitFraction() )
      {
        continue;
      }
    }
    resultFar = r;
  }
}

boolean useMagnet;
onGround = (resultNear != null);
useMagnet = (resultFar != null);

if (!onGround &amp;&amp; !useMagnet)
{
  vars.release();
  return;
}

rigidBody.getGravity(upPush);
upPush.negateLocal();
upPush.normalizeLocal();

if ( onGround )
{
  isInJump = false;
  
  float fraction = resultNear.getHitFraction() * 2;

  fraction = (float) Math.pow(fraction, 100);
  //fraction = (float) Math.pow(2, func * func * func);
  //fraction = fraction - 1;
  if ( fraction &lt; 0.0001f )
  {
    fraction = 0.0001f;
  }
  upPush.multLocal(gravityLength+ (1/fraction) );
}
else if ( ! isInJump )
{
  float fraction = (resultFar.getHitFraction() - 0.5f)* 2;
  fraction = (float) Math.pow(fraction, 200);
  if ( fraction &lt; 0.00001f )
  {
    fraction = 0.00001f;
  }
  upPush.multLocal(- (1/fraction) );
}

rigidBody.applyCentralForce(upPush);


vars.release();

}

@Override
public void update(float tpf)
{
float cylinderHalfHeight = cylinderHalfHeight();

TempVars vars;
vars = TempVars.get();

Vector3f ballLocation = vars.vect1;
Vector3f localizedLocation = vars.vect2;
Vector3f shift = vars.vect3;


rigidBody.getPhysicsLocation(ballLocation);
localizedLocation.set(ballLocation);

rigidBody.getGravity(shift);
shift.normalizeLocal();
shift.multLocal(cylinderHalfHeight + stepHeight);

localizedLocation.addLocal(shift);
//spatial.localToWorld(ballLocation, localizedLocation);
//spatial.setLocalTranslation(localizedLocation);
applyPhysicsTransform(localizedLocation, rotation);


vars.release();

}

private float cylinderHalfHeight()
{
return (height - stepHeight) / 2;
}

@Override
public void prePhysicsTick(PhysicsSpace space, float tpf)
{
applyVerticalRecoveryAndMagnet(tpf);
applyWalkDirection();

if ( jump &amp;&amp; onGround )
{
  jump = false;
  isInJump = true;
  applyJump(tpf);
}

}

private void applyWalkDirection()
{
if ( walkDirection.equals(Vector3f.ZERO) )
{
if ( onGround )
{
rigidBody.setLinearVelocity(Vector3f.ZERO);
}

  return;
}

TempVars vars;
vars = TempVars.get();

float angle;
Vector3f normalizedWalkDirection = vars.vect1;
Vector3f normalizedGravity = vars.vect2;
Vector3f precVerticalVelocity = vars.vect3;
Vector3f finalVelocity = vars.vect4;


normalizedWalkDirection.set(walkDirection);
normalizedWalkDirection.normalizeLocal();

getGravity(normalizedGravity);
normalizedGravity.normalizeLocal();

angle = normalizedWalkDirection.angleBetween(normalizedGravity);
if (angle &lt; 0)
{
  angle = -angle;
}

if (angle &lt; FastMath.FLT_EPSILON)
{
  vars.release();
  return;
}

Vector3f direction = vars.vect3;
Vector3f plan = vars.vect4;


normalizedGravity.cross(normalizedWalkDirection, direction);
direction.cross(normalizedGravity, plan);

normalizedWalkDirection.projectLocal(plan);
normalizedWalkDirection.normalizeLocal();


finalVelocity.set(normalizedWalkDirection);
finalVelocity.multLocal(15);
  
if ( ! onGround )
{
  /* keep the old vertical velocity */
  rigidBody.getLinearVelocity(precVerticalVelocity);
  precVerticalVelocity.projectLocal(normalizedGravity);

  finalVelocity.addLocal(precVerticalVelocity);
}
rigidBody.setLinearVelocity(finalVelocity);

vars.release();

}

private void applyJump(float tpf)
{
TempVars vars;
vars = TempVars.get();

Vector3f gravity = vars.vect1;
Vector3f v = vars.vect2;
Vector3f precVelocity = vars.vect3;

rigidBody.getGravity(gravity);
float l;
l = gravity.length();

float g = l / mass;
v.set(gravity);
v.normalizeLocal();

float speedLength = jumpHeight * g * 2;
speedLength = (float) Math.sqrt(speedLength);
v.multLocal(-speedLength);

rigidBody.getLinearVelocity(precVelocity);
v.addLocal(precVelocity);

rigidBody.setLinearVelocity(v);

vars.release();

}

protected final void calculateNewForward(Quaternion rotation, Vector3f direction, Vector3f worldUpVector)
{
if (direction == null)
{
return;
}
TempVars vars = TempVars.get();
Vector3f newLeft = vars.vect1;
Vector3f newLeftNegate = vars.vect2;

newLeft.set(worldUpVector).crossLocal(direction).normalizeLocal();
if (newLeft.equals(Vector3f.ZERO))
{
  if (direction.x != 0)
  {
    newLeft.set(direction.y, -direction.x, 0f).normalizeLocal();
  }
  else
  {
    newLeft.set(0f, direction.z, -direction.y).normalizeLocal();
  }
  logger.log(Level.INFO, "Zero left for direction {0}, up {1}", new Object[]
          {
            direction, worldUpVector
          });
}
newLeftNegate.set(newLeft).negateLocal();
direction.set(worldUpVector).crossLocal(newLeftNegate).normalizeLocal();
if (direction.equals(Vector3f.ZERO))
{
  direction.set(Vector3f.UNIT_Z);
  logger.log(Level.INFO, "Zero left for left {0}, up {1}", new Object[]
          {
            newLeft, worldUpVector
          });
}
if (rotation != null)
{
  rotation.fromAxes(newLeft, worldUpVector, direction);
}
vars.release();

}

@Override
protected void createSpatialData(Spatial spat)
{
rigidBody.setUserObject(spatial);
}

@Override
protected void removeSpatialData(Spatial spat)
{
rigidBody.setUserObject(null);

}

@Override
protected void setPhysicsLocation(Vector3f vec)
{
rigidBody.setPhysicsLocation(vec);
}

public void warp(Vector3f vec)
{
setPhysicsLocation(vec);
}

@Override
protected void setPhysicsRotation(Quaternion quat)
{
rotation.set(quat);
rotation.multLocal(rotatedViewDirection.set(viewDirection));
updateLocalViewDirection();
}

@Override
protected void addPhysics(PhysicsSpace space)
{
space.getGravity(localUp).normalizeLocal().negateLocal();
updateLocalCoordinateSystem();

space.addCollisionObject(rigidBody);
space.addTickListener(this);

}

@Override
protected void removePhysics(PhysicsSpace space)
{
space.removeCollisionObject(rigidBody);
space.removeTickListener(this);
}

@Override
public Control cloneForSpatial(Spatial spatial)
{
HovercraftCharacterControl control = new HovercraftCharacterControl(radius, height, mass, stepHeight);
return control;

}

@Override
public void physicsTick(PhysicsSpace space, float tpf)
{

}

public void getGravity(Vector3f gravity)
{
rigidBody.getGravity(gravity);
}

}
[/java]