Youtube video

chasecamera.png

Memo:

  1. jMonkeyEngine calls the update() methods of all AppState objects in the order in which you attached them.

  2. jMonkeyEngine calls the controlUpdate() methods of all controls in the order in which you added them.

  3. jMonkeyEngine calls the simpleUpdate() method of the main SimpleApplication class.

—AbstractPhysicBodyContext

package org.jmonkey3.chasecam;

import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.BulletAppState;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public abstract class AbstractPhysicBodyContext extends AbstractAppState
{

    private AppStateManager stateManager = null;
    //
    private static final BulletAppState bulletAppState;

    static
    {
        bulletAppState = new BulletAppState();
    }

    public AbstractPhysicBodyContext()
    {
    }

    /**
     @return the bulletAppState
     */
    public BulletAppState getBulletAppState()
    {
        return bulletAppState;
    }

    /**
     @param stateManager the stateManager to set
     Attaching BulletAppstate to Initialize PhysicsSpace
     */
    public void attachBulletAppstate(AppStateManager stateManager)
    {
        this.stateManager = stateManager;
        stateManager.attach(bulletAppState);
    }
}

—AbstractSpatialBodyContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.state.AbstractAppState;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public abstract class AbstractSpatialBodyContext extends AbstractAppState
{

    public AbstractSpatialBodyContext()
    {
    }

}

—ApplicationContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import org.jmonkey.utils.Debug;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class ApplicationContext extends AbstractAppState
{

    private final Node rootNode;
//
    private final CameraContext sceneCameraContext;
    private final AvatarBodyManager avatarBodyManager;
    private final SceneBodyManager sceneBodyManager;

/**

    @param stateManager
    @param am
    @param settings
    @param inputManager
    @param rootNode
    @param cam
    @param flyByCam
    */
    public ApplicationContext(AppStateManager stateManager, AssetManager am, AppSettings settings, InputManager inputManager, Node rootNode, Camera cam, FlyByCamera flyByCam)
    {

        this.rootNode = rootNode;
        this.sceneCameraContext = new CameraContext(settings, inputManager, cam, flyByCam);
        this.sceneBodyManager = new SceneBodyManager(stateManager, am, rootNode);
        this.avatarBodyManager = new AvatarBodyManager(am, rootNode, sceneCameraContext);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        //super.initialize(stateManager, app);
        //TODO: initialize your AppState, e.g. attach spatials to rootNode
        //this is called on the OpenGL thread after the AppState has been attached

//
        stateManager.attach(this.sceneCameraContext);
        stateManager.attach(this.sceneBodyManager);//initialize physic spacein constructor
        stateManager.attach(this.avatarBodyManager);
        //
        Debug.showNodeAxes(app.getAssetManager(), this.rootNode, 1024.0f);
        Debug.attachWireFrameDebugGrid(app.getAssetManager(), rootNode, Vector3f.ZERO, 2048, ColorRGBA.DarkGray);
    }

    @Override
    public void update(float tpf)
    {

    }
}

—AvatarAnimationEventListener.java

package org.jmonkey3.chasecam;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.scene.Spatial;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarAnimationEventListener extends AbstractAppState implements AnimEventListener
{

    private final AnimChannel channel;
    private final AnimControl control;
    private final PlayerInputActionListener pial;
    private final AvatarAnimationHelper animHelper;
    private final PhysicsCharacter physicBody;
/**

    @param pial
    @param pc
    @param avatarMesh
    */
    public AvatarAnimationEventListener(PlayerInputActionListener pial, PhysicsCharacter pc, Spatial avatarMesh)
    {
        this.pial = pial;
        this.control = avatarMesh.getControl(AnimControl.class);
        assert (this.control != null);
        this.channel = this.control.createChannel();
        this.physicBody = pc;
        this.animHelper = new AvatarAnimationHelper(this.physicBody, this.channel);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        this.control.addListener(this);
        this.channel.setAnim("Idle1");
        this.channel.setSpeed(0.5f);
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName)
    {
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    public void onAnimChange(AnimControl control, AnimChannel channel, String animName)
    {
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.

    }

    /**
     @return the channel
     */
    protected AnimChannel getChannel()
    {
        return channel;
    }

    /**
     @return the control
     */
    protected AnimControl getControl()
    {
        return control;
    }

    /**
     * @return the animHelper
     */
    protected AvatarAnimationHelper getAnimHelper()
    {
        return animHelper;
    }
}

—AvatarAnimationHelper.java

package org.jmonkey3.chasecam;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.LoopMode;
import com.jme3.bullet.objects.PhysicsCharacter;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarAnimationHelper
{

    private final AnimChannel animChannel;
    private final PhysicsCharacter physicBody;
/**

    @param pc
    @param ac
    */
    public AvatarAnimationHelper(PhysicsCharacter pc, AnimChannel ac)
    {
        this.animChannel = ac;
        this.physicBody = pc;
    }

    protected void idle()
    {
        animChannel.setAnim("Idle1");
        animChannel.setSpeed(0.5f);
    }

    protected boolean forward(boolean pressed)
    {
        if (pressed)
        {
            if (this.physicBody.onGround())
            {
                animChannel.setAnim("Walk");
                animChannel.setSpeed(AvatarConstants.FORWARD_MOVE_SPEED * 2f);
                animChannel.setLoopMode(LoopMode.Loop);
            }
            return true;
        } else
        {
            idle();
            return false;
        }
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    protected boolean backward(boolean pressed)
    {
        if (pressed)
        {
            return true;
        } else
        {
            return false;
        }
    }

    protected boolean rightward(boolean pressed)
    {
        if (pressed)
        {
            return true;
        } else
        {
            return false;
        }
    }

    protected boolean leftward(boolean pressed)
    {
        if (pressed)
        {
            return true;
        } else
        {
            return false;
        }
    }

    protected boolean jump(boolean pressed)
    {
            if (pressed)
            {
                if (this.physicBody.onGround())
                {
                    animChannel.setAnim("HighJump");
                    animChannel.setSpeed(AvatarConstants.FORWARD_MOVE_SPEED / 1.8f);
                    animChannel.setLoopMode(LoopMode.DontLoop);
                    //
                    this.physicBody.jump();
                }
                return true;
            } else
            {
                return false;
            }
    }
}

—AvatarBodyManager.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.input.ChaseCamera;
import com.jme3.input.InputManager;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import org.jmonkey.utils.Debug;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarBodyManager extends AbstractPhysicBodyContext
{

    private InputManager inputManager;
    private final Node rootNode;
    //
    private final CameraContext cc;
    private final Camera cam;
    private final ChaseCamera chaseCam;
    //
    private final AvatarPhysicBodyContext apbc;
    private final AvatarSpatialBodyContext asbc;
    //
    private final PhysicsCharacter physicBody;
    private final Node avatar;
    private final BetterCharacterControl bcc;
    //

    private final PlayerInputActionListener playerInputListener;

/**

    @param am
    @param rootNode
    @param cc
    */
    public AvatarBodyManager(AssetManager am, Node rootNode, CameraContext cc)
    {

        //
        this.rootNode = rootNode;
        //
        this.asbc = new AvatarSpatialBodyContext(am, rootNode);
        this.apbc = new AvatarPhysicBodyContext();
        //
        this.physicBody = apbc.getPhysicBody();

        this.avatar = asbc.getAvatar();
        this.bcc = new BetterCharacterControl(AvatarConstants.COLLISION_SHAPE_RADIUS, AvatarConstants.COLLISION_SHAPE_RADIUS * 2, AvatarConstants.PHYSIC_BODY_MASS);
        //
        this.playerInputListener = new PlayerInputActionListener(this.physicBody, this.asbc.getAvatarMesh());
        //
        this.cc = cc;
        this.cam = cc.getCam();
        this.chaseCam = cc.getChaseCam();
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        //TODO: initialize your AppState, e.g. attach spatials to rootNode
        //this is called on the OpenGL thread after the AppState has been attached

        stateManager.attach(this.asbc);
        stateManager.attach(this.apbc);
        stateManager.attach(this.playerInputListener);


//
        this.avatar.addControl(new AvatarBodyMoveControl(playerInputListener, physicBody, cam));
        this.avatar.addControl(chaseCam);
        this.avatar.addControl(bcc);

        //DEBUG
        Debug.showNodeAxes(app.getAssetManager(), avatar, 4);
        getBulletAppState().getPhysicsSpace().enableDebug(app.getAssetManager());
    }



    @Override
    public void update(float tpf)
    {
        //assert (sceneCameraContext != null);

        //correctDirectionVectors(cam.getDirection(), cam.getLeft());

    }
}

—AvatarBodyMoveControl.java

package org.jmonkey3.chasecam;

import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarBodyMoveControl extends AbstractControl
{
    private final Camera cam;
    private final PhysicsCharacter physicBody;
    private final PlayerInputActionListener pial;
/**

    @param pial
    @param physicBody
    @param cam
    */
    public AvatarBodyMoveControl(PlayerInputActionListener pial, PhysicsCharacter physicBody, Camera cam)
    {
        this.pial = pial;
        this.physicBody = physicBody;
        this.cam = cam;
    }
    private final Vector3f walkDirection = new Vector3f();

    @Override
    protected void controlUpdate(float tpf)
    {
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        correctDirectionVectors();
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp)
    {
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }


        /**

     @param camDir
     @param camLeft
     */
    public void correctDirectionVectors()
    {
//        assert (camDir != null);
//        assert (camLeft != null);
//        assert (walkDirection != null);
        //Affect forward, backward move speed 0.6f lower - 1.0f faster
        Vector3f camDirVector = cam.getDirection().clone().multLocal(AvatarConstants.FORWARD_MOVE_SPEED);
        //Affect left, right move speed 0.6f lower - 1.0f faster
        Vector3f camLeftVector = cam.getLeft().clone().multLocal(AvatarConstants.SIDEWARD_MOVE_SPEED);

        walkDirection.set(0, 0, 0);//critical
        if (pial.isLeftward())
        {
            walkDirection.addLocal(camLeftVector);
        }
        if (pial.isRightward())
        {
            walkDirection.addLocal(camLeftVector.negate());
        }
        if (pial.isForward())
        {
            walkDirection.addLocal(camDirVector);
        }
        if (pial.isBackward())
        {
            //@TODO Bug if cam direction (0, -n, 0) - character fly upwards ;)
            walkDirection.addLocal(camDirVector.negate());
        }
        physicBody.setWalkDirection(walkDirection);//Critical



        //Avoid vibration
        spatial.setLocalTranslation(physicBody.getPhysicsLocation());
        //Translate Node accordingly
        spatial.getControl(BetterCharacterControl.class).warp(physicBody.getPhysicsLocation());
        //Rotate Node accordingly to camera
        spatial.getControl(
                BetterCharacterControl.class).setViewDirection(
                cam.getDirection().negate());

    }
}

—AvatarConstants.java

package org.jmonkey3.chasecam;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarConstants
{
    public static final float COLLISION_SHAPE_CENTERAL_POINT = 0.0f;
    public static final float COLLISION_SHAPE_RADIUS = 4.0f;
    //
    public static final float PHYSIC_BODY_MASS = 1.0f;
    public static float FORWARD_MOVE_SPEED = 0.8f;
    public static float SIDEWARD_MOVE_SPEED = 0.6f;
}

—AvatarPhysicBodyContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.math.Vector3f;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarPhysicBodyContext extends AbstractPhysicBodyContext
{



    private final PhysicsCharacter physicBody;


    public AvatarPhysicBodyContext()
    {

        this.physicBody = new PhysicsCharacter(new SphereCollisionShape(AvatarConstants.COLLISION_SHAPE_RADIUS), .01f);

    }



    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
//
        assert (getBulletAppState() != null);
        System.out.println(this.getClass().getName() + ".getBulletAppState().hashCode() = " + getBulletAppState().hashCode());

//
        this.physicBody.setJumpSpeed(32);
        this.physicBody.setFallSpeed(32);
        this.physicBody.setGravity(32);
        this.physicBody.setPhysicsLocation(new Vector3f(0, 10, 0));
        //
        getBulletAppState().getPhysicsSpace().add(this.physicBody);

    }

    @Override
    public void update(float tpf)
    {
    }

    @Override
    public void cleanup()
    {
        super.cleanup();
    }

    /**
     @return the physicBody
     */
    public PhysicsCharacter getPhysicBody()
    {
        return this.physicBody;
    }
}

—AvatarSpatialBodyContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class AvatarSpatialBodyContext extends AbstractSpatialBodyContext
{

    //
    private final Node rootNode;
    //
    private final Node avatar;
    private final Spatial avatarMesh;
    private final Vector3f correction;
/**

    @param am
    @param rootNode
    */
    public AvatarSpatialBodyContext(AssetManager am, Node rootNode)
    {
        this.rootNode = rootNode;
        //
        this.avatar = new Node();
        this.avatarMesh = am.loadModel("Models/Ninja/Ninja.mesh.xml");
        this.correction = new Vector3f(
                0,
                AvatarConstants.COLLISION_SHAPE_CENTERAL_POINT - AvatarConstants.COLLISION_SHAPE_RADIUS,
                0);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {


        this.avatarMesh.setLocalScale(new Vector3f(0.05f, 0.05f, 0.05f));//Trouble with scales?
        this.avatarMesh.setLocalTranslation(this.correction);
        this.avatar.attachChild(this.avatarMesh);
        this.rootNode.attachChild(this.avatar);

        //super.initialize(stateManager, app); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     @return the avatar
     */
    public Node getAvatar()
    {
        return avatar;
    }

    /**
     * @return the avatarMesh
     */
    public Spatial getAvatarMesh()
    {
        return avatarMesh;
    }
}

—CameraContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.ChaseCamera;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.renderer.Camera;
import com.jme3.system.AppSettings;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class CameraContext extends AbstractAppState
{

    private final AppSettings settings;
    private final InputManager inputManager;
    /*
     http://hub.jmonkeyengine.org/javadoc/com/jme3/renderer/Camera.html
     public class Camera
     extends java.lang.Object
     implements Savable, java.lang.Cloneable

     Width and height are set to the current Application's settings.getWidth() and settings.getHeight() values.
     Frustum Perspective:
     Frame of view angle of 45° along the Y axis
     Aspect ratio of width divided by height
     Near view plane of 1 wu
     Far view plane of 1000 wu
     Start location at (0f, 0f, 10f).
     Start direction is looking at the origin.
     */
    private final Camera cam;
    /*
     http://hub.jmonkeyengine.org/javadoc/com/jme3/input/ChaseCamera.html
     public class ChaseCamera
     extends java.lang.Object
     implements ActionListener, AnalogListener, Control

     A camera that follows a spatial and can turn around it by dragging the mouse
     Constructs the chase camera, and registers inputs if you use this
     constructor you have to attach the cam later to a spatial doing
     spatial.addControl(chaseCamera);
     */
    private final ChaseCamera chaseCam;
    private final FlyByCamera flyByCam;

/**

    @param settings
    @param inputManager
    @param cam
    @param flyByCam
    */
    public CameraContext(AppSettings settings, InputManager inputManager, Camera cam, FlyByCamera flyByCam)
    {

        assert (settings != null);
        this.settings = settings;
        assert (inputManager != null);
        this.inputManager = inputManager;
        assert (cam != null);
        this.cam = cam;
        assert (flyByCam != null);
        this.flyByCam = flyByCam;
        this.chaseCam = new ChaseCamera(this.cam, this.inputManager);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        super.initialize(stateManager, app);
        //TODO: initialize your AppState, e.g. attach spatials to rootNode
        //this is called on the OpenGL thread after the AppState has been attached

        this.cam.setFrustumPerspective(116.0f, (settings.getWidth() / settings.getHeight()), 1.0f, 2000.0f);
        //this.flyByCam.setMoveSpeed(100);
        this.flyByCam.setEnabled(false);
    }

    /**
     @return the cam
     */
    public Camera getCam()
    {
        return cam;
    }

    /**
     @return the chaseCam
     */
    public ChaseCamera getChaseCam()
    {
        return chaseCam;
    }
}

—PlayerInputActionListener.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.objects.PhysicsCharacter;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.scene.Spatial;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class PlayerInputActionListener extends AbstractAppState implements ActionListener
{

    private final PhysicsCharacter physicBody;
//
    private boolean leftward = false;
    private boolean rightward = false;
    private boolean forward = false;
    private boolean backward = false;
    private boolean jump = false;
    private final AvatarAnimationEventListener aael;
/**

    @param pc
    @param avatar
    */
    public PlayerInputActionListener(PhysicsCharacter pc, Spatial avatar)
    {
        this.physicBody = pc;
        this.aael = new AvatarAnimationEventListener(this, this.physicBody, avatar);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        stateManager.attach(this.aael);
        //
        app.getInputManager().addMapping("LEFTWARD", new KeyTrigger(KeyInput.KEY_A));
        app.getInputManager().addMapping("RIGHTWARD", new KeyTrigger(KeyInput.KEY_D));
        app.getInputManager().addMapping("FORWARD", new KeyTrigger(KeyInput.KEY_W));
        app.getInputManager().addMapping("BACKWARD", new KeyTrigger(KeyInput.KEY_S));
        app.getInputManager().addMapping("JUMP", new KeyTrigger(KeyInput.KEY_SPACE));
        app.getInputManager().addListener(this, "LEFTWARD");
        app.getInputManager().addListener(this, "RIGHTWARD");
        app.getInputManager().addListener(this, "FORWARD");
        app.getInputManager().addListener(this, "BACKWARD");
        app.getInputManager().addListener(this, "JUMP");
        //
    }

    /**
     @param binding
     @param keyPressed
     @param tpf
     */
    public void onAction(String binding, boolean keyPressed, float tpf)
    {

        if (binding.equals("LEFTWARD"))
        {

            this.leftward = this.aael.getAnimHelper().leftward(keyPressed);

        } else if (binding.equals("RIGHTWARD"))
        {

            this.rightward = this.aael.getAnimHelper().rightward(keyPressed);

        } else if (binding.equals("FORWARD"))
        {

            this.forward = this.aael.getAnimHelper().forward(keyPressed);

        } else if (binding.equals("BACKWARD"))
        {

                this.backward = this.aael.getAnimHelper().backward(keyPressed);

        } else if (binding.equals("JUMP"))
        {

            this.jump = this.aael.getAnimHelper().jump(keyPressed);

        }
    }

    /**
     @return the leftward
     */
    public boolean isLeftward()
    {
        return this.leftward;
    }

    /**
     @return the rightward
     */
    public boolean isRightward()
    {
        return this.rightward;
    }

    /**
     @return the forward
     */
    public boolean isForward()
    {
        return this.forward;
    }

    /**
     @return the backward
     */
    public boolean isBackward()
    {
        return this.backward;
    }

    /**
     @return the jump
     */
    public boolean isJump()
    {
        return this.jump;
    }
}

—SceneBodyManager.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.scene.Node;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class SceneBodyManager extends AbstractPhysicBodyContext
{

    private final ScenePhysicBodyContext spbc;
    private final SceneSpatialBodyContext ssbc;

/**

    @param stateManager
    @param am
    @param rootNode
    */
    public SceneBodyManager(AppStateManager stateManager, AssetManager am, Node rootNode)
    {


        this.ssbc = new SceneSpatialBodyContext(am, rootNode);
        this.spbc = new ScenePhysicBodyContext(ssbc.getScene());
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {

        //PhysicsSpace Initialization
        attachBulletAppstate(stateManager);
//
        stateManager.attach(this.ssbc);
        stateManager.attach(this.spbc);

    }
}

—ScenePhysicBodyContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.scene.Node;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class ScenePhysicBodyContext extends AbstractPhysicBodyContext
{
    private final RigidBodyControl rigidBodyControl;
    private final Node scene;

/**

    @param scene
    */
    public ScenePhysicBodyContext(Node scene)
    {
        this.scene = scene;
        this.rigidBodyControl = new RigidBodyControl(.0f);
    }


    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        //
        //Add scene to PhysicsSpace
        System.out.println(this.getClass().getName() + ".getBulletAppState().hashCode() = " + getBulletAppState().hashCode());
        scene.addControl(rigidBodyControl);
        getBulletAppState().getPhysicsSpace().addAll(scene);
    }

}

—SceneSpatialBodyContext.java

package org.jmonkey3.chasecam;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class SceneSpatialBodyContext extends AbstractSpatialBodyContext
{

    private final Node rootNode;
    //
    private final Node scene;
    private AmbientLight ambient;
    private DirectionalLight sun;
/**

    @param am
    @param rootNode
    */
    public SceneSpatialBodyContext(AssetManager am, Node rootNode)
    {
        this.rootNode = rootNode;
        //
        am.registerLocator("town.zip", ZipLocator.class);
        this.scene = (Node) am.loadModel("main.scene");
        this.ambient = new AmbientLight();
        this.sun = new DirectionalLight();
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app)
    {
        //Main Scene loading

        this.scene.setLocalScale(0.1f);
        this.scene.scale(32.0f);
        //
        this.sun.setDirection(new Vector3f(1.4f, -1.4f, -1.4f));
        this.scene.setLocalTranslation(Vector3f.ZERO);
        //

        rootNode.attachChild(this.scene);
        rootNode.addLight(this.ambient);
        rootNode.addLight(this.sun);
    }

    /**
     @return the scene
     */
    public Node getScene()
    {
        return scene;
    }
}

—TheGame.java

package org.jmonkey3.chasecam;

import com.jme3.app.SimpleApplication;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class TheGame extends SimpleApplication
{

    private ApplicationContext applicationContext;

    public TheGame()
    {
    }

    //
    public static void main(String[] args)
    {
        TheGame game = new TheGame();
        game.setShowSettings(false);
        game.start();
    }

    @Override
    public void simpleInitApp()
    {
        this.applicationContext = new ApplicationContext(stateManager, assetManager, settings, inputManager, rootNode, cam, flyCam);
        //
        stateManager.attach(applicationContext);
    }
}

—Debug.java

package org.jmonkey.utils;

import com.jme3.animation.AnimControl;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.debug.Grid;
import com.jme3.scene.debug.SkeletonDebugger;
import com.jme3.scene.shape.Line;
import static org.jmonkey.utils.SpatialUtils.makeGeometry;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class Debug
{

    public static void showNodeAxes(AssetManager am, Node n, float axisLen)
    {
        Vector3f v = new Vector3f(axisLen, 0, 0);
        Arrow a = new Arrow(v);
        Material mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Red);
        Geometry geom = new Geometry(n.getName() + "XAxis", a);
        geom.setMaterial(mat);
        n.attachChild(geom);


        //
        v = new Vector3f(0, axisLen, 0);
        a = new Arrow(v);
        mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Green);
        geom = new Geometry(n.getName() + "YAxis", a);
        geom.setMaterial(mat);
        n.attachChild(geom);


        //
        v = new Vector3f(0, 0, axisLen);
        a = new Arrow(v);
        mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom = new Geometry(n.getName() + "ZAxis", a);
        geom.setMaterial(mat);
        n.attachChild(geom);
    }

    //
    public static void showVector3fArrow(AssetManager am, Node n, Vector3f v, ColorRGBA color, String name)
    {
        Arrow a = new Arrow(v);
        Material mat = MaterialUtils.makeMaterial(am, "Common/MatDefs/Misc/Unshaded.j3md", color);
        Geometry geom = makeGeometry(a, mat, name);
        n.attachChild(geom);
    }

    public static void showVector3fLine(AssetManager am, Node n, Vector3f v, ColorRGBA color, String name)
    {
        Line l = new Line(v.subtract(v), v);
        Material mat = MaterialUtils.makeMaterial(am, "Common/MatDefs/Misc/Unshaded.j3md", color);
        Geometry geom = makeGeometry(l, mat, name);
        n.attachChild(geom);
    }

//Skeleton Debugger
    public static void attachSkeleton(AssetManager am, Node player, AnimControl control)
    {
        SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton());
        Material mat2 = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Yellow);
        mat2.getAdditionalRenderState().setDepthTest(false);
        skeletonDebug.setMaterial(mat2);
        player.attachChild(skeletonDebug);
    }

    ///
    public static void attachWireFrameDebugGrid(AssetManager assetManager, Node n, Vector3f pos, Integer size, ColorRGBA color)
    {
        Geometry g = new Geometry("wireFrameDebugGrid", new Grid(size, size, 1.0f));//1WU
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.getAdditionalRenderState().setWireframe(true);
        mat.setColor("Color", color);
        g.setMaterial(mat);
        g.center().move(pos);
        n.attachChild(g);
    }
}

—MaterialUtils.java

package org.jmonkey.utils;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;

/*
 Chase camera (aka 3rd person camera) example
 Based on official TestQ3.java

 @author Alex Cham aka Jcrypto
 */
public class MaterialUtils
{

    public MaterialUtils()
    {
    }


    //"Common/MatDefs/Misc/Unshaded.j3md"
    public static Material makeMaterial(AssetManager am, String name, ColorRGBA color)
    {
        Material mat = new Material(am, name);
        mat.setColor("Color", color);
        return mat;
    }
}

—SpatialUtils.java

package org.jmonkey.utils;

import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;

/**

 @author java
 */
public class SpatialUtils
{
    //
    public static Node makeNode(String name)
    {
        Node n = new Node(name);
        return n;
    }

//
    public static Geometry makeGeometry(Mesh mesh, Material mat, String name)
    {
        Geometry geom = new Geometry(name, mesh);
        geom.setMaterial(mat);
        return geom;
    }

    //
    public static Geometry makeGeometry(Vector3f loc, Vector3f scl, Mesh mesh, Material mat, String name)
    {
        Geometry geom = new Geometry(name, mesh);
        geom.setMaterial(mat);
        geom.setLocalTranslation(loc);
        geom.setLocalScale(scl);
        return geom;
    }
}