[Complete] AutoRotateCamera

MMORPG Style 3rd Person Camera for Players




AutoRotateCamera

Pros:

*camera that follows CharacterControl or VehicleControl of a target spatial

*if the control rotates, the camera rotates

*adjustable zoom, horizontal rotation, vertical rotation

*adjusted vertical rotation is added as an offset to the current rotation of the control

*good use with a control that is setting its viewdirection / forward vector accordingly, preferably a character control which is using left/right movement keys as left/right rotation

Cons:

*no effect if no character control / vehicle control is assigned



PhysicalAutoRotateCamera extension of AutoRotateCamera

Pros:

*adds physical collision checks

*if an physical object is between the camera and the target to look at, the camera zooms in and places itself infront of the object

*that means that line of sight to the target will be preserved

Cons:

*does not work correctly if the camera has to zoom in too closely, thus if minimum distance (specified by user) is greater than the distance the camera has to zoom in

*no effect if no character control / vehicle control is assigned







down below are 3 code samples:

  1. TestCase
  2. AutoRotateCamera
  3. PhysicalAutoRotateCamera







    _______________________________________________________________________________________________________

    TestWalkingChar_orig.java - Move with w,a,s,d / click and drag left mouse for rotating / hold right mouse for strafing

    _______________________________________________________________________________________________________



    [java collapse=“true”]

    /**
  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */

    package mygame;



    import utility.PhysicalAutoRotateCamera;

    import com.jme3.animation.AnimChannel;

    import com.jme3.animation.AnimControl;

    import com.jme3.animation.AnimEventListener;

    import com.jme3.animation.LoopMode;

    import com.jme3.bullet.BulletAppState;

    import com.jme3.app.SimpleApplication;

    import com.jme3.bounding.BoundingBox;

    import com.jme3.bullet.PhysicsSpace;

    import com.jme3.bullet.collision.PhysicsCollisionEvent;

    import com.jme3.bullet.collision.PhysicsCollisionListener;

    import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;

    import com.jme3.bullet.collision.shapes.SphereCollisionShape;

    import com.jme3.bullet.control.CharacterControl;

    import com.jme3.bullet.control.RigidBodyControl;

    import com.jme3.bullet.util.CollisionShapeFactory;

    import com.jme3.effect.EmitterSphereShape;

    import com.jme3.effect.ParticleEmitter;

    import com.jme3.effect.ParticleMesh.Type;

    import com.jme3.input.ChaseCamera;

    import com.jme3.input.KeyInput;

    import com.jme3.input.MouseInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.input.controls.MouseButtonTrigger;

    import com.jme3.light.DirectionalLight;

    import com.jme3.material.Material;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector2f;

    import com.jme3.math.Vector3f;

    import com.jme3.post.FilterPostProcessor;

    import com.jme3.post.filters.BloomFilter;

    import com.jme3.renderer.Camera;

    import com.jme3.renderer.queue.RenderQueue.ShadowMode;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.Node;

    import com.jme3.scene.Spatial;

    import com.jme3.scene.shape.Box;

    import com.jme3.scene.shape.Sphere;

    import com.jme3.scene.shape.Sphere.TextureMode;

    import com.jme3.terrain.geomipmap.TerrainLodControl;

    import com.jme3.terrain.geomipmap.TerrainQuad;

    import com.jme3.terrain.heightmap.AbstractHeightMap;

    import com.jme3.terrain.heightmap.ImageBasedHeightMap;

    import com.jme3.texture.Texture;

    import com.jme3.texture.Texture.WrapMode;

    import com.jme3.util.SkyFactory;

    import java.util.ArrayList;

    import java.util.List;

    import jme3test.bullet.BombControl;

    import jme3tools.converters.ImageToAwt;



    /**

    *
  • @author normenhansen

    /

    public class TestWalkingChar_orig extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener {

    public static final Quaternion YAW090 = new Quaternion().fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0));

    public static final Quaternion ROT_LEFT = new Quaternion().fromAngleAxis(FastMath.PI/32, new Vector3f(0,1,0));

    public static final Quaternion ROT_RIGHT = new Quaternion().fromAngleAxis(-FastMath.PI/32, new Vector3f(0,1,0));



    private BulletAppState bulletAppState;

    //character

    CharacterControl character;

    Node model;

    //temp vectors

    Vector3f walkDirection = new Vector3f();

    //terrain

    TerrainQuad terrain;

    RigidBodyControl terrainPhysicsNode;

    //Materials

    Material matRock;

    Material matWire;

    Material matBullet;

    //animation

    AnimChannel animationChannel;

    AnimChannel shootingChannel;

    AnimControl animationControl;

    float airTime = 0;

    //camera

    boolean left = false, right = false, up = false, down = false, strafeEnabled=false;

    //ChaseCamera chaseCam;

    PhysicalAutoRotateCamera chaseCam;

    //bullet

    Sphere bullet;

    SphereCollisionShape bulletCollisionShape;

    //explosion

    ParticleEmitter effect;

    //brick wall

    Box brick;

    float bLength = 0.8f;

    float bWidth = 0.4f;

    float bHeight = 0.4f;

    FilterPostProcessor fpp;



    public static void main(String[] args) {

    TestWalkingChar_orig app = new TestWalkingChar_orig();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    bulletAppState = new BulletAppState();

    bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);

    stateManager.attach(bulletAppState);

    setupKeys();

    prepareBullet();

    prepareEffect();

    createLight();

    createSky();

    createTerrain();

    createWall();

    createCharacter();

    setupChaseCamera();

    setupAnimationController();

    setupFilter();

    }



    private void setupFilter() {

    FilterPostProcessor fpp = new FilterPostProcessor(assetManager);

    BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);

    fpp.addFilter(bloom);

    viewPort.addProcessor(fpp);

    }



    private PhysicsSpace getPhysicsSpace() {

    return bulletAppState.getPhysicsSpace();

    }



    private void setupKeys() {

    inputManager.addMapping(“wireframe”, new KeyTrigger(KeyInput.KEY_T));

    inputManager.addListener(this, “wireframe”);

    inputManager.addMapping(“CharLeft”, new KeyTrigger(KeyInput.KEY_A));

    inputManager.addMapping(“CharRight”, new KeyTrigger(KeyInput.KEY_D));

    inputManager.addMapping(“CharUp”, new KeyTrigger(KeyInput.KEY_W));

    inputManager.addMapping(“CharDown”, new KeyTrigger(KeyInput.KEY_S));

    inputManager.addMapping(“CharSpace”, new KeyTrigger(KeyInput.KEY_RETURN));

    inputManager.addMapping(“CharShoot”, new KeyTrigger(KeyInput.KEY_SPACE));

    inputManager.addMapping(“CharStrafe”, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));

    inputManager.addListener(this, “CharLeft”);

    inputManager.addListener(this, “CharRight”);

    inputManager.addListener(this, “CharUp”);

    inputManager.addListener(this, “CharDown”);

    inputManager.addListener(this, “CharSpace”);

    inputManager.addListener(this, “CharShoot”);

    inputManager.addListener(this, “CharStrafe”);

    }



    private void createWall() {

    float xOff = -144;

    float zOff = -40;

    float startpt = bLength / 4 - xOff;

    float height = 6.1f;

    brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);

    brick.scaleTextureCoordinates(new Vector2f(1f, .5f));

    for (int j = 0; j < 15; j++) {

    for (int i = 0; i < 4; i++) {

    Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff);

    addBrick(vt);

    }

    startpt = -startpt;

    height += 1.01f * bHeight;

    }

    }



    private void addBrick(Vector3f ori) {

    Geometry reBoxg = new Geometry(“brick”, brick);

    reBoxg.setMaterial(matRock);

    reBoxg.setLocalTranslation(ori);

    reBoxg.addControl(new RigidBodyControl(1.5f));

    reBoxg.setShadowMode(ShadowMode.CastAndReceive);

    this.rootNode.attachChild(reBoxg);

    this.getPhysicsSpace().add(reBoxg);

    }



    private void prepareBullet() {

    bullet = new Sphere(32, 32, 0.4f, true, false);

    bullet.setTextureMode(TextureMode.Projected);

    bulletCollisionShape = new SphereCollisionShape(0.4f);

    matBullet = new Material(getAssetManager(), “Common/MatDefs/Misc/SolidColor.j3md”);

    matBullet.setColor(“Color”, ColorRGBA.Green);

    matBullet.setColor(“m_GlowColor”, ColorRGBA.Green);

    getPhysicsSpace().addCollisionListener(this);

    }



    private void prepareEffect() {

    int COUNT_FACTOR = 1;

    float COUNT_FACTOR_F = 1f;

    effect = new ParticleEmitter(“Flame”, Type.Triangle, 32 * COUNT_FACTOR);

    effect.setSelectRandomImage(true);

    effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F)));

    effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));

    effect.setStartSize(1.3f);

    effect.setEndSize(2f);

    effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));

    effect.setParticlesPerSec(0);

    effect.setGravity(-5f);

    effect.setLowLife(.4f);

    effect.setHighLife(.5f);

    effect.setInitialVelocity(new Vector3f(0, 7, 0));

    effect.setVelocityVariation(1f);

    effect.setImagesX(2);

    effect.setImagesY(2);

    Material mat = new Material(assetManager, “Common/MatDefs/Misc/Particle.j3md”);

    mat.setTexture(“Texture”, assetManager.loadTexture(“Effects/Explosion/flame.png”));

    effect.setMaterial(mat);

    effect.setLocalScale(100);

    rootNode.attachChild(effect);

    }



    private void createLight() {

    Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();

    DirectionalLight dl = new DirectionalLight();

    dl.setDirection(direction);

    dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));

    rootNode.addLight(dl);

    }



    private void createSky() {

    rootNode.attachChild(SkyFactory.createSky(assetManager, “Textures/Sky/Bright/BrightSky.dds”, false));

    }



    private void createTerrain() {

    matRock = new Material(assetManager, “Common/MatDefs/Terrain/TerrainLighting.j3md”);

    matRock.setBoolean(“useTriPlanarMapping”, false);

    matRock.setBoolean(“WardIso”, true);

    matRock.setTexture(“AlphaMap”, assetManager.loadTexture(“Textures/Terrain/splat/alphamap.png”));

    Texture heightMapImage = assetManager.loadTexture(“Textures/Terrain/splat/mountains512.png”);

    Texture grass = assetManager.loadTexture(“Textures/Terrain/splat/grass.jpg”);

    grass.setWrap(WrapMode.Repeat);

    matRock.setTexture(“DiffuseMap”, grass);

    matRock.setFloat(“DiffuseMap_0_scale”, 64);

    Texture dirt = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);

    dirt.setWrap(WrapMode.Repeat);

    matRock.setTexture(“DiffuseMap_1”, dirt);

    matRock.setFloat(“DiffuseMap_1_scale”, 16);

    Texture rock = assetManager.loadTexture(“Textures/Terrain/splat/road.jpg”);

    rock.setWrap(WrapMode.Repeat);

    matRock.setTexture(“DiffuseMap_2”, rock);

    matRock.setFloat(“DiffuseMap_2_scale”, 128);

    Texture normalMap0 = assetManager.loadTexture(“Textures/Terrain/splat/grass_normal.png”);

    normalMap0.setWrap(WrapMode.Repeat);

    Texture normalMap1 = assetManager.loadTexture(“Textures/Terrain/splat/dirt_normal.png”);

    normalMap1.setWrap(WrapMode.Repeat);

    Texture normalMap2 = assetManager.loadTexture(“Textures/Terrain/splat/road_normal.png”);

    normalMap2.setWrap(WrapMode.Repeat);

    matRock.setTexture(“NormalMap”, normalMap0);

    matRock.setTexture(“NormalMap_1”, normalMap2);

    matRock.setTexture(“NormalMap_2”, normalMap2);

    matWire = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);

    matWire.setColor(“Color”, ColorRGBA.Green);



    AbstractHeightMap heightmap = null;

    try {

    heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f);

    heightmap.load();



    } catch (Exception e) {

    e.printStackTrace();

    }



    terrain = new TerrainQuad(“terrain”, 65, 513, heightmap.getHeightMap());

    List<Camera> cameras = new ArrayList<Camera>();

    cameras.add(getCamera());

    TerrainLodControl control = new TerrainLodControl(terrain, cameras);

    terrain.addControl(control);

    terrain.setMaterial(matRock);

    terrain.setModelBound(new BoundingBox());

    terrain.updateModelBound();

    terrain.setLocalScale(new Vector3f(2, 2, 2));



    terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);

    terrain.addControl(terrainPhysicsNode);

    rootNode.attachChild(terrain);

    getPhysicsSpace().add(terrainPhysicsNode);

    }



    private void createCharacter() {

    CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 2f);

    character = new CharacterControl(capsule, 0.1f);

    model = (Node) assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);

    model.setLocalScale(0.5f);

    model.addControl(character);

    character.setPhysicsLocation(new Vector3f(-140, 10, -10));

    rootNode.attachChild(model);

    getPhysicsSpace().add(character);

    }



    private void setupChaseCamera() {

    flyCam.setEnabled(false);

    chaseCam = new PhysicalAutoRotateCamera(cam,model,inputManager,

    bulletAppState.getPhysicsSpace());

    /


    chaseCam = new ChaseCamera(cam, model, inputManager);

    chaseCam.setSmoothMotion(true);

    chaseCam.setChasingSensitivity(1f);

    chaseCam.setTrailingEnabled(false);

    *

    */





    }



    private void setupAnimationController() {

    animationControl = model.getControl(AnimControl.class);

    animationControl.addListener(this);

    animationChannel = animationControl.createChannel();

    shootingChannel = animationControl.createChannel();

    shootingChannel.addBone(animationControl.getSkeleton().getBone(“uparm.right”));

    shootingChannel.addBone(animationControl.getSkeleton().getBone(“arm.right”));

    shootingChannel.addBone(animationControl.getSkeleton().getBone(“hand.right”));

    }





    @Override

    public void simpleUpdate(float tpf) {

    System.out.println(tpf);

    Vector3f viewDir = character.getViewDirection().normalize();

    Vector3f leftDir = YAW090.mult(viewDir).normalize();

    viewDir.y = 0;

    walkDirection.set(0, 0, 0);

    if (left) {

    if (!strafeEnabled) {

    ROT_LEFT.multLocal(viewDir);

    } else {

    walkDirection.addLocal(leftDir.mult(0.2f));

    }

    }

    if (right) {

    if (!strafeEnabled) {

    ROT_RIGHT.multLocal(viewDir);

    } else {

    walkDirection.addLocal(leftDir.negate().mult(0.2f));

    }

    }

    if (up) {

    walkDirection.addLocal(viewDir.mult(0.2f));

    //character.getControllerId().setVelocityForTimeInterval(new javax.vecmath.Vector3f(0,0.5f,0), 1000f);

    }

    if (down) {

    walkDirection.addLocal(viewDir.negate().mult(0.1f));

    }

    if (!character.onGround()) {

    airTime = airTime + tpf;

    } else {

    airTime = 0;

    }

    if (walkDirection.length() == 0) {

    if (!“stand”.equals(animationChannel.getAnimationName())) {

    animationChannel.setAnim(“stand”, 1f);

    }

    } else {



    if (airTime > .3f) {

    if (!“stand”.equals(animationChannel.getAnimationName())) {

    animationChannel.setAnim(“stand”);

    }

    } else if (!“Walk”.equals(animationChannel.getAnimationName())) {

    animationChannel.setAnim(“Walk”, 0.7f);

    }

    }

    character.setViewDirection(viewDir);

    character.setWalkDirection(walkDirection);

    }



    public void onAction(String binding, boolean value, float tpf) {

    if (binding.equals(“CharLeft”)) {

    if (value) {

    left = true;

    } else {

    left = false;

    }

    } else if (binding.equals(“CharRight”)) {

    if (value) {

    right = true;

    } else {

    right = false;

    }

    } else if (binding.equals(“CharUp”)) {

    if (value) {

    up = true;

    } else {

    up = false;

    }

    } else if (binding.equals(“CharDown”)) {

    if (value) {

    down = true;

    } else {

    down = false;

    }

    } else if (binding.equals(“CharSpace”)) {

    character.jump();

    } else if (binding.equals(“CharShoot”) && !value) {

    bulletControl();

    } else if (binding.equals(“CharStrafe”)) {

    if (value) {

    strafeEnabled = true;

    } else {

    strafeEnabled = false;

    }

    }

    }



    private void bulletControl() {

    shootingChannel.setAnim(“Dodge”, 0.1f);

    shootingChannel.setLoopMode(LoopMode.DontLoop);

    Geometry bulletg = new Geometry(“bullet”, bullet);

    bulletg.setMaterial(matBullet);

    bulletg.setShadowMode(ShadowMode.CastAndReceive);

    bulletg.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(2)));

    RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1);

    bulletControl.setCcdMotionThreshold(0.1f);

    bulletControl.setLinearVelocity(cam.getDirection().mult(80));

    bulletg.addControl(bulletControl);

    rootNode.attachChild(bulletg);

    getPhysicsSpace().add(bulletControl);

    }



    public void collision(PhysicsCollisionEvent event) {

    if (event.getObjectA() instanceof BombControl) {

    final Spatial node = event.getNodeA();

    effect.killAllParticles();

    effect.setLocalTranslation(node.getLocalTranslation());

    effect.emitAllParticles();

    } else if (event.getObjectB() instanceof BombControl) {

    final Spatial node = event.getNodeB();

    effect.killAllParticles();

    effect.setLocalTranslation(node.getLocalTranslation());

    effect.emitAllParticles();

    }

    }



    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {

    if (channel == shootingChannel) {

    channel.setAnim(“stand”);

    }

    }



    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {

    }

    }



    [/java]

    ________________________________________________________________________________________________________

    AutoRotateCamera.java

    ________________________________________________________________________________________________________



    [java collapse=“true”]

    /**
  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */

    package mygame;



    import com.jme3.bullet.control.CharacterControl;

    import com.jme3.bullet.control.PhysicsControl;

    import com.jme3.bullet.control.VehicleControl;

    import com.jme3.export.InputCapsule;

    import com.jme3.export.JmeExporter;

    import com.jme3.export.JmeImporter;

    import com.jme3.export.OutputCapsule;

    import com.jme3.input.InputManager;

    import com.jme3.input.KeyInput;

    import com.jme3.input.MouseInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.input.controls.MouseAxisTrigger;

    import com.jme3.input.controls.MouseButtonTrigger;

    import com.jme3.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.ViewPort;

    import com.jme3.scene.Spatial;

    import com.jme3.scene.control.Control;

    import java.io.IOException;



    /**
  • A camera that follows a spatial and can turn around it by dragging the mouse
  • @author nehon

    */

    public class AutoRotateCamera implements ActionListener, AnalogListener, Control {



    private InputManager inputManager;

    private Camera cam = null;

    private Spatial target = null;

    private Vector3f initialUpVec;

    private PhysicsControl playerControl = null;



    private float minVerticalRotation = 0.00f;

    private float maxVerticalRotation = FastMath.PI / 2;

    private float minDistance = 1.0f;

    private float maxDistance = 40.0f;

    private float zoomSpeed = 2f;

    private float rotationSpeed = 1.0f;



    private float camRotation = 0;

    private float camVRotation = FastMath.PI / 6;

    private float camDistance = 20;



    private Quaternion qCamRotation = Quaternion.IDENTITY;

    private Vector3f rotatedCamDir = Vector3f.ZERO;

    private Vector3f playerDir = Vector3f.ZERO;

    private Vector3f camLoc = Vector3f.ZERO;



    private boolean canRotate;

    private boolean enabled = true;



    private enum possibleControl {CharacterControl,VehicleControl};

    private possibleControl selectedControl = null;



    /**
  • Constructs the chase camera
  • @param cam the application camera
  • @param target the spatial to follow

    */

    public AutoRotateCamera(Camera cam, final Spatial target) {

    this.setSpatial(target);

    this.cam = cam;

    initialUpVec = cam.getUp().clone();

    computePosition();

    target.addControl(this);

    cam.setLocation(camLoc);

    }



    /**
  • Constructs the chase camera, and registers inputs
  • @param cam the application camera
  • @param target the spatial to follow
  • @param inputManager the inputManager of the application to register inputs

    */

    public AutoRotateCamera(Camera cam, final Spatial target, InputManager inputManager) {

    this(cam, target);

    registerWithInput(inputManager);

    }



    public void onAction(String name, boolean keyPressed, float tpf) {

    if (name.equals("camToggleRotate") && enabled) {

    if (keyPressed) {

    canRotate = true;

    inputManager.setCursorVisible(false);

    } else {

    canRotate = false;

    inputManager.setCursorVisible(true);

    }

    }





    }



    //change Mouse Axis here (swap the negations)

    public void onAnalog(String name, float value, float tpf) {

    if (name.equals("camMouseLeft")) {

    rotateCamera(value);

    } else if (name.equals("camMouseRight")) {

    rotateCamera(-value);

    } else if (name.equals("camUp")) {

    vRotateCamera(value);

    } else if (name.equals("camDown")) {

    vRotateCamera(-value);

    } else if (name.equals("camZoomIn")) {

    zoomCamera(value);

    } else if (name.equals("camZoomOut")) {

    zoomCamera(-value);

    }



    }



    /**
  • Registers inputs with the input manager
  • @param inputManager

    */

    public void registerWithInput(InputManager inputManager) {

    String[] inputs = {"camToggleRotate", "camDown", "camUp", "camMouseLeft", "camMouseRight", "camZoomIn", "camZoomOut"};



    this.inputManager = inputManager;



    inputManager.addMapping("camDown", new MouseAxisTrigger(1, true));

    inputManager.addMapping("camUp", new MouseAxisTrigger(1, false));

    inputManager.addMapping("camZoomIn", new MouseAxisTrigger(2, true));

    inputManager.addMapping("camZoomOut", new MouseAxisTrigger(2, false));

    inputManager.addMapping("camMouseLeft", new MouseAxisTrigger(0, true));

    inputManager.addMapping("camMouseRight", new MouseAxisTrigger(0, false));

    inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

    inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));



    inputManager.addListener(this, inputs);





    }



    //computes the position of the camera

    private void computePosition() {

    qCamRotation = qCamRotation.fromAngleNormalAxis(camRotation, initialUpVec);

    if (playerDir != null) {

    rotatedCamDir = qCamRotation.mult(playerDir).normalize();

    } else {

    rotatedCamDir = qCamRotation.mult(Vector3f.UNIT_XYZ);

    rotatedCamDir.y = 0;

    rotatedCamDir.normalizeLocal();

    rotatedCamDir.y = 0;

    }

    rotatedCamDir.addLocal(0, FastMath.sin(camVRotation), 0);

    camLoc = rotatedCamDir.mult(camDistance);

    camLoc.addLocal(target.getWorldTranslation());

    }



    //rotate the camera around the target on the horizontal plane

    private void rotateCamera(float value) {

    if (!canRotate || !enabled) {

    return;

    }

    camRotation += value * rotationSpeed;

    }



    //move the camera toward or away the target

    private void zoomCamera(float value) {

    if (!enabled) {

    return;

    }



    camDistance += value * zoomSpeed;

    if (camDistance > maxDistance) {

    camDistance = maxDistance;

    }

    if (camDistance < minDistance) {

    camDistance = minDistance;

    }

    if ((camVRotation (minDistance + 1.0f))) {

    camVRotation = minVerticalRotation;

    }

    }



    //rotate the camera around the target on the vertical plane

    private void vRotateCamera(float value) {

    if (!canRotate || !enabled) {

    return;

    }



    camVRotation += value * rotationSpeed;

    if (camVRotation > maxVerticalRotation) {

    camVRotation = maxVerticalRotation;

    }

    if ((camVRotation (minDistance + 1.0f))) {

    camVRotation = minVerticalRotation;

    }

    }

    /**
  • Updates the camera, should only be called internally

    */

    protected void updateCamera(float tpf) {

    if (enabled) {

    playerDir=getViewDirection();

    computePosition();

    cam.setLocation(camLoc);

    cam.lookAt(target.getWorldTranslation(), initialUpVec);

    }

    }



    /**
  • Return the enabled/disabled state of the camera
  • @return true if the camera is enabled

    */

    public boolean isEnabled() {

    return enabled;

    }



    /**
  • Enable or disable the camera
  • @param enabled true to enable

    */

    public void setEnabled(boolean enabled) {

    this.enabled = enabled;

    if (!enabled) {

    canRotate = false; // reset this flag in-case it was on before

    }

    }



    /**
  • Returns the max zoom distance of the camera (default is 40)
  • @return maxDistance

    */

    public float getMaxDistance() {

    return maxDistance;

    }



    /**
  • Sets the max zoom distance of the camera (default is 40)
  • @param maxDistance

    */

    public void setMaxDistance(float maxDistance) {

    this.maxDistance = maxDistance;

    }



    /**
  • Returns the min zoom distance of the camera (default is 1)
  • @return minDistance

    */

    public float getMinDistance() {

    return minDistance;

    }



    /**
  • Sets the min zoom distance of the camera (default is 1)
  • @return minDistance

    */

    public void setMinDistance(float minDistance) {

    this.minDistance = minDistance;

    }



    /**
  • clone this camera for a spatial
  • @param spatial
  • @return

    */

    public Control cloneForSpatial(Spatial spatial) {

    AutoRotateCamera cc = new AutoRotateCamera(cam, spatial, inputManager);

    cc.setMaxDistance(getMaxDistance());

    cc.setMinDistance(getMinDistance());

    return cc;

    }



    /**
  • Sets the spacial for the camera control, should only be used internally
  • @param spatial

    */

    public void setSpatial(Spatial spatial) {



    target = spatial;

    selectedControl = null;



    VehicleControl vehicleControl = target.getControl(VehicleControl.class);

    if (vehicleControl != null) {

    this.playerControl = vehicleControl;

    this.selectedControl = possibleControl.VehicleControl;

    }

    CharacterControl characterControl = target.getControl(CharacterControl.class);

    if (characterControl != null) {

    this.playerControl = characterControl;

    this.selectedControl = possibleControl.CharacterControl;

    }



    }



    private Vector3f getViewDirection() {

    if (this.selectedControl==possibleControl.CharacterControl) {

    return ((CharacterControl)playerControl).getViewDirection().clone();

    }

    if (this.selectedControl==possibleControl.VehicleControl) {

    return ((VehicleControl)playerControl).getForwardVector(null).clone();

    }

    return null;

    }



    /**
  • update the camera control, should on ly be used internally
  • @param tpf

    */

    public void update(float tpf) {

    updateCamera(tpf);

    }



    /**
  • renders the camera control, should on ly be used internally
  • @param rm
  • @param vp

    */

    public void render(RenderManager rm, ViewPort vp) {

    //nothing to render

    }



    /**
  • Write the camera
  • @param ex the exporter
  • @throws IOException

    */

    public void write(JmeExporter ex) throws IOException {

    OutputCapsule capsule = ex.getCapsule(this);

    capsule.write(maxDistance, "maxDistance", 40);

    capsule.write(minDistance, "minDistance", 1);

    }



    /**
  • Read the camera
  • @param im
  • @throws IOException

    */

    public void read(JmeImporter im) throws IOException {

    InputCapsule ic = im.getCapsule(this);

    maxDistance = ic.readFloat("maxDistance", 40);

    minDistance = ic.readFloat("minDistance", 1);

    }



    /**

    *
  • @deprecated use getMaxVerticalRotation()

    */

    @Deprecated

    public float getMaxHeight() {

    return getMaxVerticalRotation();

    }



    /**

    *
  • @deprecated use setMaxVerticalRotation()

    */

    @Deprecated

    public void setMaxHeight(float maxHeight) {

    setMaxVerticalRotation(maxHeight);

    }



    /**

    *
  • @deprecated use getMinVerticalRotation()

    */

    @Deprecated

    public float getMinHeight() {

    return getMinVerticalRotation();

    }



    /**

    *
  • @deprecated use setMinVerticalRotation()

    */

    @Deprecated

    public void setMinHeight(float minHeight) {

    setMinVerticalRotation(minHeight);

    }



    /**
  • returns the maximal vertical rotation angle of the camera around the target
  • @return

    */

    public float getMaxVerticalRotation() {

    return maxVerticalRotation;

    }



    /**
  • sets the maximal vertical rotation angle of the camera around the target default is Pi/2;
  • @param maxVerticalRotation

    */

    public void setMaxVerticalRotation(float maxVerticalRotation) {

    this.maxVerticalRotation = maxVerticalRotation;

    }



    /**
  • returns the minimal vertical rotation angle of the camera around the target
  • @return

    */

    public float getMinVerticalRotation() {

    return minVerticalRotation;

    }



    /**
  • sets the minimal vertical rotation angle of the camera around the target default is 0;
  • @param minHeight

    */

    public void setMinVerticalRotation(float minHeight) {

    this.minVerticalRotation = minHeight;

    }



    /**
  • Sets the default distance at start of applicaiton
  • @param defaultDistance

    */

    public void setDefaultDistance(float defaultDistance) {

    camDistance = defaultDistance;

    }



    /**
  • sets the default horizontal rotation of the camera at start of the application
  • @param angle

    */

    public void setDefaultHorizontalRotation(float angle) {

    camRotation = angle;

    }



    /**
  • sets the default vertical rotation of the camera at start of the application
  • @param angle

    */

    public void setDefaultVerticalRotation(float angle) {

    camVRotation = angle;

    }



    }

    [/java]

    _________________________________________________________________________________________________________

    PhysicalAutoRotateCamera.java

    _________________________________________________________________________________________________________

    [java collapse=“true”]

    /**
  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */



    import com.jme3.bullet.PhysicsSpace;

    import com.jme3.bullet.collision.PhysicsRayTestResult;

    import com.jme3.bullet.control.CharacterControl;

    import com.jme3.bullet.control.PhysicsControl;

    import com.jme3.bullet.control.VehicleControl;

    import com.jme3.export.InputCapsule;

    import com.jme3.export.JmeExporter;

    import com.jme3.export.JmeImporter;

    import com.jme3.export.OutputCapsule;

    import com.jme3.input.InputManager;

    import com.jme3.input.MouseInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.MouseAxisTrigger;

    import com.jme3.input.controls.MouseButtonTrigger;

    import com.jme3.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.renderer.RenderManager;

    import com.jme3.renderer.ViewPort;

    import com.jme3.scene.Spatial;

    import com.jme3.scene.control.Control;

    import java.io.IOException;

    import java.util.LinkedList;



    /**
  • A camera that follows a spatial and can turn around it by dragging the mouse
  • @author nehon (ChaseCamera), Mark (PhysicalChaseCamera), nego (adaptation)

    *

    */

    public class PhysicalAutoRotateCamera implements ActionListener, AnalogListener, Control {



    private InputManager inputManager;

    private Camera cam = null;

    private Spatial target = null;

    private Vector3f initialUpVec;

    private PhysicsControl playerControl = null;

    private PhysicsSpace physicsSpace;





    private float minVerticalRotation = 0.00f;

    private float maxVerticalRotation = FastMath.PI / 2;

    private float minDistance = 1.0f;

    private float maxDistance = 40.0f;

    private float zoomSpeed = 2f;

    private float rotationSpeed = 1.0f;



    private float camRotation = 0;

    private float camVRotation = FastMath.PI / 6;

    private float camDistance = 20;



    private Quaternion qCamRotation = Quaternion.IDENTITY;

    private Vector3f rotatedCamDir = Vector3f.ZERO;

    private Vector3f playerDir = Vector3f.ZERO;

    private Vector3f camLoc = Vector3f.ZERO;



    private boolean canRotate;

    private boolean enabled = true;



    private enum possibleControl {CharacterControl,VehicleControl};

    private possibleControl selectedControl = null;





    private LinkedList<PhysicsRayTestResult> collisionResults = null;

    private float noCollisionDistance = 0f;

    private Vector3f noCollisionLoc = Vector3f.ZERO;



    /**
  • Constructs the chase camera
  • @param cam the application camera
  • @param target the spatial to follow

    */

    public PhysicalAutoRotateCamera(Camera cam, final Spatial target) {

    this.setSpatial(target);

    this.cam = cam;

    initialUpVec = cam.getUp().clone();

    computeCamPosition();

    target.addControl(this);

    cam.setLocation(camLoc);

    }



    /**
  • Constructs the chase camera, and registers inputs
  • @param cam the application camera
  • @param target the spatial to follow
  • @param inputManager the inputManager of the application to register inputs

    */

    public PhysicalAutoRotateCamera(Camera cam, final Spatial target, InputManager inputManager) {

    this(cam, target);

    registerWithInput(inputManager);

    }



    /**
  • Constructs the chase camera, and registers inputs and physicspace
  • @param cam the application camera
  • @param target the spatial to follow
  • @param inputManager the inputManager of the application to register inputs
  • @param physicsSpace the PhysicsSpace to be added for Collision testing

    */



    public PhysicalAutoRotateCamera(Camera cam, final Spatial target,

    InputManager inputManager, PhysicsSpace physicsSpace) {

    this(cam, target, inputManager);

    this.physicsSpace=physicsSpace;

    }



    public void onAction(String name, boolean keyPressed, float tpf) {

    if (name.equals("camToggleRotate") && enabled) {

    if (keyPressed) {

    canRotate = true;

    inputManager.setCursorVisible(false);

    } else {

    canRotate = false;

    inputManager.setCursorVisible(true);

    }

    }





    }



    //change Mouse Axis here (swap the negations)

    public void onAnalog(String name, float value, float tpf) {

    if (name.equals("camMouseLeft")) {

    rotateCamera(value);

    } else if (name.equals("camMouseRight")) {

    rotateCamera(-value);

    } else if (name.equals("camUp")) {

    vRotateCamera(value);

    } else if (name.equals("camDown")) {

    vRotateCamera(-value);

    } else if (name.equals("camZoomIn")) {

    zoomCamera(value);

    } else if (name.equals("camZoomOut")) {

    zoomCamera(-value);

    }



    }



    /**
  • Registers inputs with the input manager
  • @param inputManager

    */

    public void registerWithInput(InputManager inputManager) {

    String[] inputs = {"camToggleRotate", "camDown", "camUp", "camMouseLeft", "camMouseRight", "camZoomIn", "camZoomOut"};



    this.inputManager = inputManager;



    inputManager.addMapping("camDown", new MouseAxisTrigger(1, true));

    inputManager.addMapping("camUp", new MouseAxisTrigger(1, false));

    inputManager.addMapping("camZoomIn", new MouseAxisTrigger(2, true));

    inputManager.addMapping("camZoomOut", new MouseAxisTrigger(2, false));

    inputManager.addMapping("camMouseLeft", new MouseAxisTrigger(0, true));

    inputManager.addMapping("camMouseRight", new MouseAxisTrigger(0, false));

    inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

    inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));



    inputManager.addListener(this, inputs);





    }



    //computes the rotation of the relative camera, based on target rotation and

    //camera rotation

    private void computeRotation() {

    qCamRotation = qCamRotation.fromAngleNormalAxis(camRotation, initialUpVec);

    if (playerDir != null) {

    rotatedCamDir = qCamRotation.mult(playerDir).normalize();

    } else {

    rotatedCamDir = qCamRotation.mult(Vector3f.UNIT_XYZ);

    rotatedCamDir.y = 0;

    rotatedCamDir.normalizeLocal();

    rotatedCamDir.y = 0;

    }

    rotatedCamDir.addLocal(0, FastMath.sin(camVRotation), 0);

    }



    //computes the position of the camera

    private void computeCamPosition() {

    camLoc = rotatedCamDir.mult(camDistance);

    camLoc.addLocal(target.getWorldTranslation());

    }



    //computes the position of the camera with no collision

    private void computeNoCollisionPosition() {

    noCollisionLoc = rotatedCamDir.mult(noCollisionDistance);

    noCollisionLoc.addLocal(target.getWorldTranslation());

    }



    //check for collisions

    private void checkCollisions() {

    if (physicsSpace!=null) {

    collisionResults = (LinkedList) physicsSpace.rayTest(target.getWorldTranslation(), camLoc);

    float hitFraction = 1f;

    if (collisionResults != null && collisionResults.size() > 0) {

    hitFraction = collisionResults.getFirst().getHitFraction();

    noCollisionDistance = ((float) ((int) (hitFraction * 100))) / 100 * camDistance;

    } else {

    noCollisionDistance = camDistance;

    }

    } else {

    noCollisionDistance = camDistance;

    }

    }



    //rotate the camera around the target on the horizontal plane

    private void rotateCamera(float value) {

    if (!canRotate || !enabled) {

    return;

    }

    camRotation += value * rotationSpeed;

    }



    //move the camera toward or away the target

    private void zoomCamera(float value) {

    if (!enabled) {

    return;

    }



    camDistance += value * zoomSpeed;

    if (camDistance > maxDistance) {

    camDistance = maxDistance;

    }

    if (camDistance < minDistance) {

    camDistance = minDistance;

    }

    if ((camVRotation < minVerticalRotation) && (camDistance > (minDistance + 1.0f))) {

    camVRotation = minVerticalRotation;

    }

    }



    //rotate the camera around the target on the vertical plane

    private void vRotateCamera(float value) {

    if (!canRotate || !enabled) {

    return;

    }



    camVRotation += value * rotationSpeed;

    if (camVRotation > maxVerticalRotation) {

    camVRotation = maxVerticalRotation;

    }

    if ((camVRotation < minVerticalRotation) && (camDistance > (minDistance + 1.0f))) {

    camVRotation = minVerticalRotation;

    }

    }

    /**
  • Updates the camera, should only be called internally

    */

    protected void updateCamera(float tpf) {

    if (enabled) {

    playerDir=getViewDirection();

    playerDir.y=0;

    computeRotation();

    computeCamPosition();

    checkCollisions();

    computeNoCollisionPosition();

    cam.setLocation(noCollisionLoc);

    cam.lookAt(target.getWorldTranslation(), initialUpVec);

    }

    }



    /**
  • Return the enabled/disabled state of the camera
  • @return true if the camera is enabled

    */

    public boolean isEnabled() {

    return enabled;

    }



    /**
  • Enable or disable the camera
  • @param enabled true to enable

    */

    public void setEnabled(boolean enabled) {

    this.enabled = enabled;

    if (!enabled) {

    canRotate = false; // reset this flag in-case it was on before

    }

    }



    /**
  • Returns the max zoom distance of the camera (default is 40)
  • @return maxDistance

    */

    public float getMaxDistance() {

    return maxDistance;

    }



    /**
  • Sets the max zoom distance of the camera (default is 40)
  • @param maxDistance

    */

    public void setMaxDistance(float maxDistance) {

    this.maxDistance = maxDistance;

    }



    /**
  • Returns the min zoom distance of the camera (default is 1)
  • @return minDistance

    */

    public float getMinDistance() {

    return minDistance;

    }



    /**
  • Sets the min zoom distance of the camera (default is 1)
  • @return minDistance

    */

    public void setMinDistance(float minDistance) {

    this.minDistance = minDistance;

    }



    /**
  • clone this camera for a spatial
  • @param spatial
  • @return

    */

    public Control cloneForSpatial(Spatial spatial) {

    PhysicalAutoRotateCamera cc = new PhysicalAutoRotateCamera(cam, spatial, inputManager);

    cc.setMaxDistance(getMaxDistance());

    cc.setMinDistance(getMinDistance());

    return cc;

    }



    /**
  • Sets the spacial for the camera control, should only be used internally
  • @param spatial

    */

    public void setSpatial(Spatial spatial) {



    target = spatial;

    selectedControl = null;



    VehicleControl vehicleControl = target.getControl(VehicleControl.class);

    if (vehicleControl != null) {

    this.playerControl = vehicleControl;

    this.selectedControl = possibleControl.VehicleControl;

    }

    CharacterControl characterControl = target.getControl(CharacterControl.class);

    if (characterControl != null) {

    this.playerControl = characterControl;

    this.selectedControl = possibleControl.CharacterControl;

    }



    }



    private Vector3f getViewDirection() {

    if (this.selectedControl==possibleControl.CharacterControl) {

    return ((CharacterControl)playerControl).getViewDirection().clone();

    }

    if (this.selectedControl==possibleControl.VehicleControl) {

    return ((VehicleControl)playerControl).getForwardVector(null).clone();

    }

    return null;

    }



    /**
  • update the camera control, should on ly be used internally
  • @param tpf

    */

    public void update(float tpf) {

    updateCamera(tpf);

    }



    /**
  • renders the camera control, should on ly be used internally
  • @param rm
  • @param vp

    */

    public void render(RenderManager rm, ViewPort vp) {

    //nothing to render

    }



    /**
  • Write the camera
  • @param ex the exporter
  • @throws IOException

    */

    public void write(JmeExporter ex) throws IOException {

    OutputCapsule capsule = ex.getCapsule(this);

    capsule.write(maxDistance, "maxDistance", 40);

    capsule.write(minDistance, "minDistance", 1);

    }



    /**
  • Read the camera
  • @param im
  • @throws IOException

    */

    public void read(JmeImporter im) throws IOException {

    InputCapsule ic = im.getCapsule(this);

    maxDistance = ic.readFloat("maxDistance", 40);

    minDistance = ic.readFloat("minDistance", 1);

    }



    /**

    *
  • @deprecated use getMaxVerticalRotation()

    */

    @Deprecated

    public float getMaxHeight() {

    return getMaxVerticalRotation();

    }



    /**

    *
  • @deprecated use setMaxVerticalRotation()

    */

    @Deprecated

    public void setMaxHeight(float maxHeight) {

    setMaxVerticalRotation(maxHeight);

    }



    /**

    *
  • @deprecated use getMinVerticalRotation()

    */

    @Deprecated

    public float getMinHeight() {

    return getMinVerticalRotation();

    }



    /**

    *
  • @deprecated use setMinVerticalRotation()

    */

    @Deprecated

    public void setMinHeight(float minHeight) {

    setMinVerticalRotation(minHeight);

    }



    /**
  • returns the maximal vertical rotation angle of the camera around the target
  • @return

    */

    public float getMaxVerticalRotation() {

    return maxVerticalRotation;

    }



    /**
  • sets the maximal vertical rotation angle of the camera around the target default is Pi/2;
  • @param maxVerticalRotation

    */

    public void setMaxVerticalRotation(float maxVerticalRotation) {

    this.maxVerticalRotation = maxVerticalRotation;

    }



    /**
  • returns the minimal vertical rotation angle of the camera around the target
  • @return

    */

    public float getMinVerticalRotation() {

    return minVerticalRotation;

    }



    /**
  • sets the minimal vertical rotation angle of the camera around the target default is 0;
  • @param minHeight

    */

    public void setMinVerticalRotation(float minHeight) {

    this.minVerticalRotation = minHeight;

    }



    /**
  • Sets the default distance at start of applicaiton
  • @param defaultDistance

    */

    public void setDefaultDistance(float defaultDistance) {

    camDistance = defaultDistance;

    }



    /**
  • sets the default horizontal rotation of the camera at start of the application
  • @param angle

    */

    public void setDefaultHorizontalRotation(float angle) {

    camRotation = angle;

    }



    /**
  • sets the default vertical rotation of the camera at start of the application
  • @param angle

    */

    public void setDefaultVerticalRotation(float angle) {

    camVRotation = angle;

    }



    }



    [/java]
1 Like

If i understand correctly, you want the camera to have a “rubberband” effect, adjusting smoothly to the new view direction.



One way to solve this is to not modify the camera direction directly, but have another Vector3f, or Spatial that you set (or call lookAt(), on).

Then, in the update() method, you can interpolate the direction of the actual camera towards the temporary Vector3f. (You might need some additional clamps to avoid minimal adjustments).



Hope it helps a bit on the way.

2 Likes

@rickard

thank you very much for your quick reply!!



At first I thought a rubberband effect was neccessary but the only problem was my math being wrong.

I fixed it, now it works like a charm, no stuttering at all on my slow PC.



________________________________________________________________________________





INITIAL POST, SWITCHED IT WITH THE WORKING VERSION FOR BETTER OVERVIEW

____________________________________________________________________________________________________

Hello, i just joined the community, and im astonished how much good effort was put in here. The tutorials are great, you get the basics at ease.



One thing i noticed though: no documentation whatsoever on CCD physics stuff. Through search i found out what it means: Explanation with pics.



Motivation:

*The ChaseCamera is nice, trailing and motionsensing looks fantastic, but when i am controlling a character from 3rd person view, i want to see the things in front of him right away (i messed with all accessors methods, couldnt find a way though). The problem is when u walk backwards, the camera stays in front of the character sometimes, thus you see his face.



*Another thing is controlling the character with motionsensing on (although i think this can be fixed tweaking the motionsense-sensitivity): if you use TestWalkingCharacter for example and turn motionsense on, it can produce rather ackward movement (going left makes the character go in circles, going backwards seams like being drunk), as the movement of testwalkingcharacter relies on camera direction.





Temporary Solution:

*Make the camera direction the same as the view direction of the character. Camera left/rigt adjustements (through mouse drag or left/right keys) change the viewdirection of character. Character doesnt respond to Left/Right Keys. Character movement is limited to going forward in the direction of the character (and thus the camera direction).



*This works great, but the camera is too static. In some tight quarters i want to be able to rotate the camera, so that the camera rotation stays that way until i manually move the camera in line with the character again.





Wanted Solution:

*make camera rotation dynamic: when user moves camera, the camera direction, relative(!) to the characters view direction, stays all the time. Example: you move the camera so you look at the characters right shoulders. When the character rotates left, the camera has to follow and adopt its position so that it looks at the same angle on the characters right shoulders again, as before.



*I tried to implement this, but camera is wildly shacking all the time. The Character handles it rotation now, so im trying to sync the cam to the model each time. This happens by getting the viewDirection of the Character and the camDirection of the Camera. I then calculate the angle between them (which i tested isnt affected by up/down rotation of camera). The angle seems to be ok, but the camera just keeps shacking wildly.



*Can any1 give me a tip, please?





Code:

down below



TEMPORARY SOLUTION - WORKING



AutoRotateCamera.java - you attach it like a normal ChaseCam.
[java collapse="true"]
/**
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package mygame;

import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
* A camera that follows a spatial and can turn around it by dragging the mouse
* @author nehon
*/
public class AutoRotateCamera_slim implements ActionListener, AnalogListener, Control {

private InputManager inputManager;
private Vector3f initialUpVec;

private Spatial target = null;
private Camera cam = null;

private float minVerticalRotation = 0.00f;
private float maxVerticalRotation = FastMath.PI / 2;
private float minDistance = 1.0f;
private float maxDistance = 40.0f;
private float zoomSpeed = 2f;
private float rotationSpeed = 1.0f;

private float targetRotation = 0;
private float targetVRotation = FastMath.PI / 6;
private float targetDistance = 20;
private Vector3f pos;

private boolean canRotate;
private boolean enabled = true;



/**
* Constructs the chase camera
* @param cam the application camera
* @param target the spatial to follow
*/
public AutoRotateCamera_slim(Camera cam, final Spatial target) {
this.target = target;
this.cam = cam;
initialUpVec = cam.getUp().clone();
computePosition();
target.addControl(this);
cam.setLocation(pos);
}

/**
* Constructs the chase camera, and registers inputs
* @param cam the application camera
* @param target the spatial to follow
* @param inputManager the inputManager of the application to register inputs
*/
public AutoRotateCamera_slim(Camera cam, final Spatial target, InputManager inputManager) {
this(cam, target);
registerWithInput(inputManager);
}

public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("camToggleRotate") && enabled) {
if (keyPressed) {
canRotate = true;
inputManager.setCursorVisible(false);
} else {
canRotate = false;
inputManager.setCursorVisible(true);
}
}


}

public void onAnalog(String name, float value, float tpf) {
if (name.equals("camMouseLeft")) {
rotateCamera(-value);
} else if (name.equals("camMouseRight")) {
rotateCamera(value);
} else if(name.equals("camLeft")) {
canRotate=true;
rotateCamera(-value);
canRotate=false;
} else if (name.equals("camRight")) {
canRotate=true;
rotateCamera(value);
canRotate=false;
} else if (name.equals("camUp")) {
vRotateCamera(-value);
} else if (name.equals("camDown")) {
vRotateCamera(value);
} else if (name.equals("camZoomIn")) {
zoomCamera(value);
} else if (name.equals("camZoomOut")) {
zoomCamera(-value);
}

}

/**
* Registers inputs with the input manager
* @param inputManager
*/
public void registerWithInput(InputManager inputManager) {
String[] inputs = {"camLeft","camRight","camToggleRotate", "camDown", "camUp", "camMouseLeft", "camMouseRight", "camZoomIn", "camZoomOut"};

this.inputManager = inputManager;

inputManager.addMapping("camLeft", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("camRight", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("camDown", new MouseAxisTrigger(1, true));
inputManager.addMapping("camUp", new MouseAxisTrigger(1, false));
inputManager.addMapping("camZoomIn", new MouseAxisTrigger(2, true));
inputManager.addMapping("camZoomOut", new MouseAxisTrigger(2, false));
inputManager.addMapping("camMouseLeft", new MouseAxisTrigger(0, true));
inputManager.addMapping("camMouseRight", new MouseAxisTrigger(0, false));
inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addMapping("camToggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));

inputManager.addListener(this, inputs);


}

private void computePosition() {

float hDistance = (targetDistance) * FastMath.sin((FastMath.PI / 2) - targetVRotation);
pos = new Vector3f(hDistance * FastMath.cos(targetRotation), (targetDistance) * FastMath.sin(targetVRotation), hDistance * FastMath.sin(targetRotation));
pos = pos.add(target.getWorldTranslation());
}

//rotate the camera around the target on the horizontal plane
private void rotateCamera(float value) {
if (!canRotate || !enabled) {
return;
}
targetRotation += value * rotationSpeed;


}


//move the camera toward or away the target
private void zoomCamera(float value) {
if (!enabled) {
return;
}

targetDistance += value * zoomSpeed;
if (targetDistance > maxDistance) {
targetDistance = maxDistance;
}
if (targetDistance < minDistance) {
targetDistance = minDistance;
}
if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
targetVRotation = minVerticalRotation;
}
}

//rotate the camera around the target on the vertical plane
private void vRotateCamera(float value) {
if (!canRotate || !enabled) {
return;
}

targetVRotation += value * rotationSpeed;
if (targetVRotation > maxVerticalRotation) {
targetVRotation = maxVerticalRotation;
}
if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
targetVRotation = minVerticalRotation;
}
}

/**
* Updates the camera, should only be called internally
*/
protected void updateCamera(float tpf) {
if (enabled) {

//easy no smooth motion
computePosition();
cam.setLocation(pos);

//the cam looks at the target
cam.lookAt(target.getWorldTranslation(), initialUpVec);
}
}

/**
* Return the enabled/disabled state of the camera
* @return true if the camera is enabled
*/
public boolean isEnabled() {
return enabled;
}

/**
* Enable or disable the camera
* @param enabled true to enable
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (!enabled) {
canRotate = false; // reset this flag in-case it was on before
}
}

/**
* Returns the max zoom distance of the camera (default is 40)
* @return maxDistance
*/
public float getMaxDistance() {
return maxDistance;
}

/**
* Sets the max zoom distance of the camera (default is 40)
* @param maxDistance
*/
public void setMaxDistance(float maxDistance) {
this.maxDistance = maxDistance;
}

/**
* Returns the min zoom distance of the camera (default is 1)
* @return minDistance
*/
public float getMinDistance() {
return minDistance;
}

/**
* Sets the min zoom distance of the camera (default is 1)
* @return minDistance
*/
public void setMinDistance(float minDistance) {
this.minDistance = minDistance;
}

/**
* clone this camera for a spatial
* @param spatial
* @return
*/
public Control cloneForSpatial(Spatial spatial) {
AutoRotateCamera_slim cc = new AutoRotateCamera_slim(cam, spatial, inputManager);
cc.setMaxDistance(getMaxDistance());
cc.setMinDistance(getMinDistance());
return cc;
}

/**
* Sets the spacial for the camera control, should only be used internally
* @param spatial
*/
public void setSpatial(Spatial spatial) {
target = spatial;
}

/**
* update the camera control, should on ly be used internally
* @param tpf
*/
public void update(float tpf) {
updateCamera(tpf);
}

/**
* renders the camera control, should on ly be used internally
* @param rm
* @param vp
*/
public void render(RenderManager rm, ViewPort vp) {
//nothing to render
}

/**
* Write the camera
* @param ex the exporter
* @throws IOException
*/
public void write(JmeExporter ex) throws IOException {
OutputCapsule capsule = ex.getCapsule(this);
capsule.write(maxDistance, "maxDistance", 40);
capsule.write(minDistance, "minDistance", 1);
}

/**
* Read the camera
* @param im
* @throws IOException
*/
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
maxDistance = ic.readFloat("maxDistance", 40);
minDistance = ic.readFloat("minDistance", 1);
}

/**
*
* @deprecated use getMaxVerticalRotation()
*/
@Deprecated
public float getMaxHeight() {
return getMaxVerticalRotation();
}

/**
*
* @deprecated use setMaxVerticalRotation()
*/
@Deprecated
public void setMaxHeight(float maxHeight) {
setMaxVerticalRotation(maxHeight);
}

/**
*
* @deprecated use getMinVerticalRotation()
*/
@Deprecated
public float getMinHeight() {
return getMinVerticalRotation();
}

/**
*
* @deprecated use setMinVerticalRotation()
*/
@Deprecated
public void setMinHeight(float minHeight) {
setMinVerticalRotation(minHeight);
}

/**
* returns the maximal vertical rotation angle of the camera around the target
* @return
*/
public float getMaxVerticalRotation() {
return maxVerticalRotation;
}

/**
* sets the maximal vertical rotation angle of the camera around the target default is Pi/2;
* @param maxVerticalRotation
*/
public void setMaxVerticalRotation(float maxVerticalRotation) {
this.maxVerticalRotation = maxVerticalRotation;
}

/**
* returns the minimal vertical rotation angle of the camera around the target
* @return
*/
public float getMinVerticalRotation() {
return minVerticalRotation;
}

/**
* sets the minimal vertical rotation angle of the camera around the target default is 0;
* @param minHeight
*/
public void setMinVerticalRotation(float minHeight) {
this.minVerticalRotation = minHeight;
}

/**
* Sets the default distance at start of applicaiton
* @param defaultDistance
*/
public void setDefaultDistance(float defaultDistance) {
targetDistance = defaultDistance;
}

/**
* sets the default horizontal rotation of the camera at start of the application
* @param angle
*/
public void setDefaultHorizontalRotation(float angle) {
targetRotation = angle;
}

/**
* sets the default vertical rotation of the camera at start of the application
* @param angle
*/
public void setDefaultVerticalRotation(float angle) {
targetVRotation = angle;
}
}[/java]





TestWalkingCharacter - modified version of the Character to allow new Camera, only update method changed and the camera declaration
*simpleUpdate method
[java]
@Override
public void simpleUpdate(float tpf) {
Vector3f camDir = cam.getDirection().clone().multLocal(0.2f);
camDir.y = 0;
walkDirection.set(0, 0, 0);
if (up) {
walkDirection.addLocal(camDir);
}
if (down) {
walkDirection.addLocal(camDir.negate());
}
if (!player.onGround()) {
airTime = airTime + tpf;
} else {
airTime = 0;
}
if (walkDirection.length() == 0) {
if (!"stand".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("stand", 1f);
}
} else {
if (airTime > .3f) {
if (!"stand".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("stand");
}
} else if (!"Walk".equals(animationChannel.getAnimationName())) {
animationChannel.setAnim("Walk", 0.7f);
}
}
player.setViewDirection(camDir);
player.setWalkDirection(walkDirection);
}
[/java]
*camera definition
[java]
flyCam.setEnabled(false);
chaseCam = new AutoRotateCamera(cam, playerModel, inputManager);
[/java]


REMOVED WANTED SOLUTION, CHECK FIRST POST FOR THE WORKING, COMPLETE, WANTED SOLUTION
2 Likes

Thanks for this, I’ll take a closer look when I have a bit more time. Seems like it has a few benefits :slight_smile:

Yeah i just saw you posted a game with a truck and i heard you had some problems with slow camera rotation in corners :slight_smile:



You should definetly check it out, as i think this is exactly what you need :slight_smile: (it works only if you have a vehicle control attached though)

PhysicalAutoRotateCamera:



based on PhysicalChaseCamera, checks for collisions, so that the camera zooms in when it detects an obstacle in physical world.

Same limitations apply as for PhysicalChaseCamera



check first post

um what exactly does this do, can u post a test case, maybe I’m doing something wrong but this behaves no different than the the normal chaseCam
for me
, in fact your temporary solution posted above was actually quite interesting, thought this was an improved version of that actually, can see that working for vehicles and such.

Does the chase cam turn immediately when the character/vehicle looks in a different location? → This is solved by autorotatecamera

If you played WoW, this should be an imitation of the camera used in there



EDIT: code sample added

EDIT2: code sample moved to first post

ok never mind didn’t realize I had to make changes the update cycle the other one seemed to just work :? my bad sorry



the physical versions definitely going crazy though

Nps, i hope that this can proove useful for you.

I posted code samples up there



What do you mean by going crazy? Is there something not working? :frowning:

this is kinda going crazy too, but only when I rotate with the left/right key, my guess is something is causing the camera to spin really really really fast, and from the looks of thing that’s what was happennig in the other test just automatically due to something in the update.

I think the spinning has something to with the mouse axis sensitivity I had to play with the mouse almost like its won of those old combination safes to keep from spinning

are you using the latest version? - its in one of the latest posts

i had the problem in the original post i posted, but then i fixed it and put up a new version

its hard to get an overview here, as i posted much of code, i will put the working code first and delete the rest



EDIT: testcase modified and added to first post

ok the weirdness have stopped somewhat
 are u compensating for your pc performance or something, the rotation is super fast on my pc when I press left or right, that’s probably I get weird spinning when I tried to integrate it with my work
your make shift solution still works better for me at this point
 I get a nice smooth manageable turn with that

thanks mcbeth for informing me bout this issue:

yeah i am working on a netbook, thats why i didnt notice anything weird (my fps is about 12 when i am running the testclass posted above)



try multiplying the rotation values with the tpf value from the update method, this should decrease the rotation OR

modify the static variables ROT_LEFT & ROT_RIGHT by lowering the angle (FastMath.PI/256 for example)

[java]

if (left) {

if (!strafeEnabled) {

ROT_LEFT.multLocal(viewDir.mult(tpf));

} else {

walkDirection.addLocal(leftDir.mult(0.2f));

}

}

if (right) {

if (!strafeEnabled) {

ROT_RIGHT.multLocal(viewDir.mult(tpf));

} else {

walkDirection.addLocal(leftDir.negate().mult(0.2f));

}

}

[/java]



your make shift solution still works better for me at this point


. I get a nice smooth manageable turn with that


I dont quite understand what you mean with "shift" solution?
nego said:
I dont quite understand what you mean with "shift" solution?


I meant your temporary solution...................anyways I got it to work now, will still need to work on it a bit, some of the functionality you left out from the original chasecam is useful to me thank for your assist though

Np. Can you elaborate what you changed in order to get it working?

Whats missing? Smooth motion or some getters/setters?

what I changed in the tests



[java]if (left) {

if (!strafeEnabled) {

ROT_LEFT.multLocal(viewDir);

} else {

walkDirection.addLocal(leftDir.mult(viewDir.mult(tpf)));

}

}

if (right) {

if (!strafeEnabled) {

ROT_RIGHT.multLocal(viewDir);

} else {

walkDirection.addLocal(leftDir.negate().mult(viewDir.mult(tpf)));

}

}[/java]



variables

[java]fromAngleAxis(FastMath.PI/1024[/java]



as for what I would miss



the automatic trailing orientation, I realize that you can manually set the cam to trail but for my purposes it could lead to issues for the player



the ability to change the height offset, my game is a shooter, need to see from just over the characters head



the ability to disable dragging the mouse to rotate the character I need a free looking system.

1 Like

Thanks for claryfiyng: this will proove useful for other users trying to solve the rotation problem!

Just be careful when using a constant for the calculation of the rotation angle: If the fps isnt fixed (due to large graphics demand or if you are trying to make it multiplayer), pressing left/right will rotate the character faster on high fps, and pressing left/right on lower fps rates will make the rotation slower.

Something like [java]fromAngleAxis(tpf*1.5f,
)[/java] could solve that issue.



But if you are doing a 3rd person shooter, you should definitely stick to the temporary solution, as the camera is fixed as far as i can remember, and the player rotates if you rotate the camera. To disable dragging, the bool value “canRotate” must be enabled at all times.

Hope that helps!