BetterCharacterControl view direction

Relatively new here, so please be patient…

I have trouble with the BetterCharacterControl. I have checked out the latest version from SVN:
[java]
$ svn info
Path: .
URL: http://jmonkeyengine.googlecode.com/svn/trunk
Repository Root: http://jmonkeyengine.googlecode.com/svn
Repository UUID: 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
Revision: 10587
Node Kind: directory
Schedule: normal
Last Changed Author: Kaelthas_Spellsinger@o2.pl
Last Changed Rev: 10587
Last Changed Date: 2013-05-01 09:44:05 +1200 (Wed, 01 May 2013)[/java]

This is my code:
[java]
package com.gravitygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

/**

  • test

  • @author geekdenz
    */
    public class Main extends SimpleApplication implements ActionListener {

    private BulletAppState bulletAppState;
    private Spatial gameLevel;
    private RigidBodyControl landscape;
    private BetterCharacterControl player;
    private CapsuleCollisionShape smallerCapsule;
    private CapsuleCollisionShape capsuleShape;
    private RigidBodyControl otherForce;
    private Vector3f grav;

    private Node playerNode;
    private CameraNode camNode;

    private SphereCollisionShape playerSphere;
    private boolean left = false, right = false, up = false, down = false;
    private Vector3f walkDirection = new Vector3f();
    private Vector3f gravity = new Vector3f();
    private Node physicsNode;
    private CollisionShape sceneShape;
    private Quaternion rot = new Quaternion();
    private Vector3f dir = new Vector3f(-9.81f,0f,0f);
    private Vector3f upVector = new Vector3f(0, 1, 0);
    private Node wholeScene;
    public static void main(String[] args) {
    Main app = new Main();
    app.start();
    }

    private void setUpLight() {
    // We add light so we see the scene
    AmbientLight al = new AmbientLight();
    al.setColor(ColorRGBA.White.mult(1.3f));
    rootNode.addLight(al);

     DirectionalLight dl = new DirectionalLight();
     dl.setColor(ColorRGBA.White);
     //dl.setDirection(new Vector3f(-2.8f, -2.8f, 0f).normalizeLocal());
     dl.setDirection(new Vector3f(-1f, -1f, -2f).normalizeLocal());
     
     rootNode.addLight(dl);
     
     DirectionalLight dl1 = new DirectionalLight();
    
     dl1.setColor(ColorRGBA.White);
     //dl.setDirection(new Vector3f(-2.8f, -2.8f, 0f).normalizeLocal());
     dl1.setDirection(new Vector3f(-1f, -1f, 2f).normalizeLocal());
     
     rootNode.addLight(dl1);
    
     DirectionalLight dl2 = new DirectionalLight();
    
     dl2.setColor(ColorRGBA.White);
     //dl.setDirection(new Vector3f(-2.8f, -2.8f, 0f).normalizeLocal());
     dl2.setDirection(new Vector3f(1f, 0f, 2f).normalizeLocal());
     
     rootNode.addLight(dl2);
    

    }

    private void setUpKeys() {
    mouseInput.setCursorVisible(false);
    inputManager.setCursorVisible(true);
    inputManager.addMapping(“Left”, new KeyTrigger(KeyInput.KEY_A));
    inputManager.addMapping(“Right”, new KeyTrigger(KeyInput.KEY_D));
    inputManager.addMapping(“Up”, new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping(“Down”, new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping(“Jump”, new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping(“j”, new KeyTrigger(KeyInput.KEY_J));
    inputManager.addMapping(“h”, new KeyTrigger(KeyInput.KEY_H));
    inputManager.addMapping(“l”, new KeyTrigger(KeyInput.KEY_L));
    inputManager.addMapping(“k”, new KeyTrigger(KeyInput.KEY_K));
    inputManager.addListener(this, “Left”);
    inputManager.addListener(this, “Right”);
    inputManager.addListener(this, “Up”);
    inputManager.addListener(this, “Down”);
    inputManager.addListener(this, “Jump”);
    inputManager.addListener(this, “gLeft”);
    inputManager.addListener(this, “gRight”);
    inputManager.addListener(this, “gUp”);
    inputManager.addListener(this, “gDown”);
    inputManager.addListener(this, “j”);
    inputManager.addListener(this, “h”);
    inputManager.addListener(this, “l”);
    inputManager.addListener(this, “k”);
    }

    /**

    • These are our custom actions triggered by key presses. We do not walk
    • yet, we just keep track of the direction the user pressed.
      */
      public void onAction(String binding, boolean value, float tpf) {
      if (binding.equals(“Left”)) {
      left = value;
      } else if (binding.equals(“Right”)) {
      right = value;
      } else if (binding.equals(“Up”)) {
      up = value;
      } else if (binding.equals(“Down”)) {
      down = value;
      } else if (binding.equals(“Jump”)) {
      player.jump();
      } else if (binding.equals(“j”)) {
      if (value) {
      } else {
      }
      } else if (binding.equals(“h”) && value) {
      } else if (binding.equals(“l”) && value) {
      } else if (binding.equals(“k”) && value) {
      }

    }

    @Override
    public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);

     setUpKeys();
     setUpLight();
     initScene();
     initPlayer();
     initPhysics();
    

    }

    @Override
    public void simpleUpdate(float tpf) {
    Vector3f camDir = cam.getDirection().clone();
    Vector3f camLeft = cam.getLeft().clone();
    walkDirection.set(0, 0, 0);
    if (left) {
    walkDirection.addLocal(camLeft);
    }
    if (right) {
    walkDirection.addLocal(camLeft.negate());
    }
    if (up) {
    walkDirection.addLocal(camDir);
    }
    if (down) {
    walkDirection.addLocal(camDir.negate());
    }
    walkDirection.multLocal(tpf*1000);
    walkDirection.setY(0);
    player.setWalkDirection(walkDirection);
    player.setViewDirection(camDir);
    }

    @Override
    public void simpleRender(RenderManager rm) {
    //TODO: add render code
    }

    private void initScene() {
    gameLevel = assetManager.loadModel(“Models/box3d/box3d.j3o”);
    gameLevel.setLocalTranslation(0, -5.2f, 0);
    gameLevel.setLocalScale(2);
    rootNode.attachChild(gameLevel);
    sceneShape = CollisionShapeFactory.createMeshShape((Node) gameLevel);
    landscape = new RigidBodyControl(sceneShape, 0);
    }

    private void initPlayer() {
    player = new BetterCharacterControl(0.5f, 1.9f, 80f);
    bulletAppState.getPhysicsSpace().add(player);
    camNode = new CameraNode(“camera”, cam);
    camNode.addControl(player);
    rootNode.attachChild(camNode);
    }

    private void initPhysics() {
    bulletAppState.getPhysicsSpace().add(landscape);
    bulletAppState.getPhysicsSpace().enableDebug(assetManager);
    //bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0f,0f,0f));
    }
    }
    [/java]

The problem is that player.setViewDirection(camDir) seems to screw up the whole thing, but without it, when I move the mouse, nothing happens.

Please help!
I know the latest version might still have bugs, but that is also why I’m posting here.

To clarify: I would like to be able to control the character with my keyboard and mouse. Currently, it only works fine with the keyboard. When I use the mouse to change the view direction, nothing happens, and when I add player.setViewDirection(camDir) the keyboard movements go into opposite directions and get slowed down which makes controlling the character next to impossible.

Any pointers would be much appreciated.

What do you mean by “screw up”?

I think I answered your question before you asked it. :slight_smile:

You still have a FlyCam enabled there, are you aware of that?

Oh yes. But disabling it didn’t improve anything. Now the view direction doesn’t change and the other keyboard controls misbehave as well. When I hit W for forward I go sideways etc.

Thanks for the quick reply rate!

The viewdirection works fine for me, I don’t really know what you want to do in your test so I can’t say if it works normal. Note that the final viewDirection for the model is always perpendicular to the local z-axis. Look at TestBetterCharacter for an example.

Just a standard WASD first person shooter control, where W is forward, A is left strafe, S is backwards, D is right strafe and most importantly and not working, the mouse changing the view direction in all 4 directions, ie up, down, left, right.

Do I need a more recent version of the code maybe?

Have you tried this exact code with

[java]
flyCam.setEnabled(false);
[/java]
?

Have you tried the exact code of the example? It allows you to move the character like in a normal game, as you describe.

<cite>@geekdenz said:</cite> Just a standard WASD first person shooter control, where W is forward, A is left strafe, S is backwards, D is right strafe and most importantly and not working, the mouse changing the view direction in all 4 directions, ie up, down, left, right.

Do I need a more recent version of the code maybe?

Have you tried this exact code with

[java]
flyCam.setEnabled(false);
[/java]
?

That may not work like you want it to depending on where you call it. Best thing is if you don’t want the fly cam then just don’t add it… or remove the app state. Search for “remove flycam” and I guess there are probably 50 hits.

Thanks Normen! Hadn’t thought of looking for the example. Duh!

However, the example is from a 3rd person perspective and I would like to do things from the first person perspective. Is there a better control for that with which you can change the gravity direction as well?

I’m envisaging a game where gravity changes directions depending on events in the game. It should be a first person “shooter” if you like but with weapons around gravity. The intention is to make it open source as it will be my second game I’ve been working on with jMonkeyEngine and I am a strong proponent of OS. But it is the first that takes real advantage of 3D physics as opposed to 2D like Salvage Team Rush (http://sourceforge.net/projects/salvageteam/) that I’ve been working on with my friend conzar.

To partially answer my own question:

I will need to use the inputManager and add a Mouse Trigger to it similar to this:
[java]
inputManager.addMapping(“Mouse Right”,
new MouseAxisTrigger(MouseInput.AXIS_X, true));
[/java]

I might do another post if I get things working.

I managed to find it out. If you would like a TestBetterCharacterFPS class, please let me know and I can spend some time making it. Basically, it is the same as the TestBetterCharacter class with some minor modifications.

I am subscribed to this thread, so please let me know here. I would also be happy to contribute to the core of the project because jme is the best engine i’ve come across, multi platform and Java being the top reasons for this. But also the rest, it seems very well designed and as easy to use as it can be really.

If you press some key (don’t remember which… Enter? Space?) the TestBetterCharacter has a FPS mode already.

Yes, you are right, it has. But you can see the character from behind with that (Enter) and cannot control the view with the mouse. However, it was quite easy to add the mouse controls with the inputManager API.

However, the hard work had been done with the BetterCharacterControl. One of my requirements is that one can change the gravity vector with the character control and this now works! In fact, I haven’t found something similar in Unity, so JME is a winner since Java is also my favorite language.

Hi again,

Got a bit further now with my game…

However, one thing I cannot get working is the camera view direction (yet again). It works fine by moving the mouse etc. I simply enabled the flycam and it gets the direction correctly when moving in one gravity field.

BUT when the gravity field changes, the view direction changes. Relative to the player’s up vector it doesn’t change, but because the up vector has changed, from one frame to the next, the BetterCharacterControl changes the cameras view direction by the rotation of the up vector around the normal of the plane that the two up vectors span by the angle between the two different up vectors. Following that logic, I thought I could simply rotate the camera view direction back around the same normal vector and have the perceived view direction not changed, even though the gravity of the character has changed.

This would be the behaviour that I want, because it is confusing if suddenly the player looks into a different direction.

However, this approach didn’t work in practice. The view direction doesnt change. I checked the change and it definitely happens.

As I write this, I realise, it might have to do with how the input is handled. Will have a look if that is the case, but please do not hesitate to provide some input/feedback if you have an idea.

Read the javadoc of the BetterCharacter, you run into the exact issue of gimbal lock and local coordinates it is made to solve.

Thanks (again) for a very quick answer!

I’m guessing this is a clue:
[java]
/**
* Sets the view direction for the character. Note this only defines the
* rotation of the spatial in the local x/z plane of the character.
*
* @param vec
*/
public void setViewDirection(Vector3f vec) {
viewDirection.set(vec);
updateLocalViewDirection();
}
[/java]

It updates the local view direction. When that is updated it changes relative to the characters x/z plane.

I still don’t quite understand. I read all the javadoc and even parts of the code but cannot figure out what is wrong. I also read about gimbal lock and local coordinates. I don’t think I get the gimbal lock problem as that only occurs when the two plane defining vectors are exactly opposite to each other (from my understanding of gimbal lock), which can happen but is very unlikely unless you have a sphere inside a sphere where the different gravity vectors are exactly opposite to each other.

Tbh your comment was a bit short. To me it could mean a few things:

  1. I definitely have the gimbal lock and local coordinates problem and this is to be solved, but is not yet.
  2. I have the gimbal lock problem and local coordinates solves this problem.
  3. I could also run into the gimbal lock problem and local coordinates solves this problem.
  4. ?

Can you please use a few more words?

Thanks!

Update:
Here: http://www.ogre3d.org/tikiwiki/Quaternion+and+Rotation+Primer#Quaternion_Rotations and in other places it says that quaternions avoid gimbal lock. So, the only problem really is if the gravity vectors are the negate of one-another, which is not the case in my test.

I should have done this first, checked my input:
My angle is 0, although that seems weird. I do:
[java]
Vector3f viewDirection = app.getViewDirection();
Vector3f last = lastSource.getDownDirection(characterNode);
Vector3f now = one.getDownDirection(characterNode);
Vector3f normal = last.cross(now).normalizeLocal();
float angle = last.angleBetween(now);
Quaternion quat = new Quaternion();
quat.fromAngleAxis(angle, normal);
System.out.println("last = “+ last +” now “+ now +” angle “+ angle +” around "+ normal);
quat.multLocal(viewDirection);
app.setViewDirection(viewDirection);
[/java]

The output of which is:

last = (-0.0, -9.81, -0.0) now (10.41806, -14.999965, 9.917589) angle 0.0 around (-0.68949574, 0.0, 0.7242897)

Why is the angle 0.0 even though the vectors are pointing in different directions?

To answer my own question:
[java]
public float angleBetween(Vector3f otherVector)
angleBetween returns (in radians) the angle between two vectors. It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
Parameters:
otherVector - a unit vector to find the angle against
Returns:
the angle in radians.
[/java]

from the java docs :slight_smile:

However, now in practice it is rotating around the wrong axis. Can you see the thing I’m doing wrong?

[java]
Vector3f viewDirection = app.getViewDirection();
Vector3f last = lastSource.getDownDirection(characterNode).normalize();
Vector3f now = one.getDownDirection(characterNode).normalize();
Vector3f normal = last.cross(now).normalizeLocal();
float angle = last.angleBetween(now);
Quaternion quat = new Quaternion();
quat.fromAngleAxis(angle, normal);
System.out.println("last = “+ last +” now “+ now +” angle “+ angle +” around "+ normal);
quat.multLocal(viewDirection);
app.setViewDirection(viewDirection);
[/java]