Zelda Camera Collision and Rotation

Hello,



I try to make the same camera system as Zelda OOT and now I try to make the camera flexible as possible. So, now i’ve problem



Just look:



http://i.imgur.com/jOhhs.jpg



So i don’t know when i’ve to move the camera. I try to detect the collision with a ray and the camera Left but i use the distance of the closest collision and if i’m already inside a geometry the results is wrong and the camera turn in the wrong sense. Moreover the camera the camera has to detect collisions and move behind the characters.



If you want more detail please let me know.

Yeah , the code is kind of “ugly” … Heh heh, I don’t think he intended to be “copied and pasted” by lazy dude like us… In fact i’m using it too but still get some annoying problems. So, I plan my self improve it to become a “well-coded” one. I will spend sometime to re-write that code in a clearer way with clearer comment and post here…



Please give me sometime man!

@Bonechilla and @stomrage : I must say bonechilla’s code is a good start for us to write our own “intelligent chase cam”.



As I said, it still lack of “reset method” and “flexible chasing” (like a real cameraman). I also want to add “camera trigger system” which change a “chase cam” to a “fix angle cam” when player cross the “trigger”.

So the chase cam must have the “flexible chasing” to smooth out the angle changes…



<= :stuck_out_tongue: My English it’s not so good, sorry for these obscure sentences!

Cool Project you got there… !



Just a flash thought : Maybe you need to tie your chase camera with a physics collision shape and check physic collision to avoid the situation you mentioned …



And in other hand, I don’t really get what camera behaviors do you want, you can record a video and it will definitely say more than words!

http://www.youtube.com/watch?v=G5TaBHbCIPQ



Here the video

I doubt that this chase cam in Zelda can “intelligent” know when the character model is hidden by other occluded mesh (by the ray method you mention)…



I still think that “fake-intelligent” effect which we saw is really from a physic collision shape which are controlled to :


  1. follow the player as fast as possible but keep the minimum distance (determine by the physic movement vector)



    2.in case it left behind when player jump down, they needs a few steps to speed up to keep follow the player, this create the effect like a real- camera man is running behind…


  2. the physic collision shape make the camera avoid the obstacles in a “fake smart way” by slide in the obstacles sufface… So sometime in the game you will see the camera is stuck in a mountain slope, then the ray method is evolved to compute the best “reset” position and direction for cam!



    This method is used by my game company for an very old game (like 7-8 years ago) but the technique may still considerable thought :stuck_out_tongue:

Another approach could be to cast a ray from the play towards the camera, if the ray doesn’t collide with anything or the collision is further away than the cam distance, your in the clear - in theory there will be clear line of sight to the player.



If the ray does hit something, you have some options:

  • set the camera distance to the collision distance, what this will do is zoom the camera closer the the player and put it infront of whatever was getting in the way … I have used this before but its not what you want.
  • I guess you could cast another ray but rotated around the y (up) axis by an amount (lets say 5 degrees ? there is no right answer) then do the same procedure, if the ray doesn’t hit anything this time then you can rotate your camera around by the same amount and there shouldn’t be anything in between. If there is something in the way, rotate the ray by an additional 5 degrees until eventually you have a clear line of sight to the player.



    After writing all this it feels like there is probably a way more elegant solution out there, I have never looked for it so I dunno what it may be sorry.



    Good Luck!

    James

@ guy :



Take a look at this, it’s a clear and working example (need some more tweaking of course) to what i’m mention in my post :

http://hub.jmonkeyengine.org/groups/user-code-projects/forum/topic/autorotatecamera/

Thanks a lot !



http://hub.jmonkeyengine.org/members/atomix/



So if i summarize what you said atomix in Zelda OOT the camera just collide with the object and his attached to the character with an elastic. If the distance is not correct the camera speeds up. Very interesting. When I see how this camera is better than some games I think it’s still a very good solution. Besides, this system is still used in Zelda Wind Waker 5 years later.



AutoRotateCamera huhu it’s exactly what i’ve want but it’s difficult to understand for me. (I put this page in my favorites and study more in detail later :p)



http://hub.jmonkeyengine.org/members/thetoucher/



I will test your solution ! (If I can implement it)

Kyaaa thetoucher your solution destroyed my framerates huhu.

My aplogies. In zelda wind waker they use ray to detect collision and rotate the camera. See yourself:



http://www.youtube.com/watch?v=QBrK7YYcWzs



i thought thetoucher solution must be the best solution. I do it like this :

while ( !angleCorrect ){

tempAngle += 5;
if(ray distance > maxDistance ){

angleCorrect = true;

}

}

but it's consumme a lot of performance. As you can see sometime the camera turn around and sometimes the cam just zoom to the collision point.

Someone know how can i do it ?

@stomrage:

This video just tell that they use ray but doesn’t tell they don’t use “physic collision check”.



I didn’t say that they don’t use ray :stuck_out_tongue: … I said they use ray only when something goes wrong, cause ray casting cost too much performance ( it take every visible mesh in to calculation) and you cast say 360/5 = 72 rays for a circle plane and ??? rays for a whole sphere shape??



So use the physic collision for “camera-navigate” and 1 ray toward player for “camera-zoom-if need”



So here is the plan:

Step1: One cast

If the smallest object unit in you game size 1, you can check with two parallel ray which 1 unit far from each other.

—If nothing totally hide the character from the camera : It’s normal situation → Camera Navigate step.

—If something totally hide the character from the camera : Reset camera pos to the last obstacle mesh pos (find that by fire a opposite ray from your player toward camera )



Step2: Camera Navigate : Avoid things

Make a sphere shape and attach to the camera, deal with the shaking artifact of physic collision (for smooth camera moves)



I didn’t do this code but I’m going to do it soon (in my game), so if you finish it before me, I’m glad to know :stuck_out_tongue:

1 Like

P/s :http://hub.jmonkeyengine.org/groups/contribution-depot-jme3/snippets/single/16/

check this if you didn’t check before :stuck_out_tongue:

what do you mean in the step2 is to link the Camera to a CameraNode, create a PhysicsControl like RigidBodyControl with a SphereCollisionShape set the kinematic true and add the camNode in the physicSpace ? For the first step i must create a CollisionListener ?If the mesh is totally hide cast a ray.

In the link you mentionned i don’t know what he done. The method “collide()” makes me crazy. What this fu**** “hitFraction” ^^. Sorry i’m totally a dummies with Physics and Collision.



Thanks for your help and your patience.

maybe if you are using physics you can use the PhysicsRayTestResult it works pretty nice for me except that essentially its a sort of hack since i currently have to duplicate chaseCamera and change some of its access modifiers. But it does detect collision and doesn’t go inside of a mesh or say outside of a tunnel if your inside of it.



The only problem with below is that i did have to recreate chasecamera and make some fields protected instead of private however i’m not completly aware of the satate of chaseCamera right now so maybe it will work without having to duplicate the class and set the fields to protected instead of private



maybe a core member can make a real implimentation of PhysicsChaseCamera :slight_smile:





/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package com.spectre.app;



    import com.jme3.bullet.PhysicsSpace;

    import com.jme3.bullet.collision.PhysicsRayTestResult;

    import com.jme3.input.InputManager;

    import com.jme3.input.controls.JoyAxisTrigger;

    import com.jme3.math.FastMath;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.Spatial;

    import com.spectre.director.Director;

    import java.util.LinkedList;



    /**

    *
  • @author Kyle Williams

    */

    public class SpectreCamera extends ChaseCamera{

    private PhysicsSpace pSpace = null;

    private Vector3f maxPos = new Vector3f();

    private float sensitivity = 2.5f;



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

    /

    public SpectreCamera(Camera cam, final Spatial target) {

    super(cam,target);

    setUp();

    setDragToRotate(false);

    }



    private void setUp(){

    inputManager = Director.getApp().getInputManager();

    setMaxVerticalRotation(FastMath.PI /3.5f);

    setMinVerticalRotation(-FastMath.PI /4f);

    setMaxDistance(6.0f);

    setMinDistance(6.0f);

    computePosition();

    registerInput(inputManager);

    registerWithInput(inputManager);

    }



    private void collide() {

    //compute position

    float hDistance = (targetDistance) * FastMath.sin((FastMath.PI / 2) - vRotation);

    maxPos = new Vector3f(hDistance * FastMath.cos(rotation), (targetDistance) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));

    maxPos = maxPos.add(target.getWorldTranslation());

    //collide

    if(pSpace==null){pSpace = Director.getPhysicsSpace();zoomCamera(6f);}//Makes sure physicsSpace is set and when it is set and zooms the camera out

    LinkedList<PhysicsRayTestResult> testResults = (LinkedList) pSpace.rayTest(target.getWorldTranslation(), maxPos);

    float hf = 1f;//hitFraction

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

    hf = testResults.getFirst().getHitFraction();

    }

    //targetDistance = ((float)((int)(hitFraction
    100)))/100 * targetDistance;

    targetDistance = hf * targetDistance;

    }



    /**
  • sets the current zoom distance for the chase camera
  • @param new distance

    */

    public void alterDistance(float alterBy) {

    this.zoomCamera(alterBy);

    }



    @Override

    protected void computePosition(){

    alterDistance(getMaxDistance());//Forces the Camera to a specific distance



    super.computePosition();

    this.collide();

    }



    /**
  • @return cam the camera registered by this controller

    /

    public Camera getCamera(){return cam;}



    public final void registerInput(InputManager inputManager) {

    String[] inputs = {ChaseCamToggleRotate,

    ChaseCamDown,

    ChaseCamUp,

    ChaseCamMoveLeft,

    ChaseCamMoveRight,

    ChaseCamZoomIn,

    ChaseCamZoomOut};



    this.inputManager = inputManager;

    if (!invertYaxis) {

    inputManager.addMapping(ChaseCamDown, new JoyAxisTrigger(0, 2, false));

    inputManager.addMapping(ChaseCamUp, new JoyAxisTrigger(0, 2, true));

    } else {

    inputManager.addMapping(ChaseCamDown, new JoyAxisTrigger(0, 2, true));

    inputManager.addMapping(ChaseCamUp, new JoyAxisTrigger(0, 2, false));

    }

    //inputManager.addMapping(ChaseCamZoomIn, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));

    //inputManager.addMapping(ChaseCamZoomOut, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));

    if(!invertXaxis){

    inputManager.addMapping(ChaseCamMoveLeft, new JoyAxisTrigger(0, 3, true));

    inputManager.addMapping(ChaseCamMoveRight, new JoyAxisTrigger(0, 3, false));

    }else{

    inputManager.addMapping(ChaseCamMoveLeft, new JoyAxisTrigger(0, 3, false));

    inputManager.addMapping(ChaseCamMoveRight, new JoyAxisTrigger(0, 3, true));

    }

    //inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

    //inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));



    inputManager.addListener(this, inputs);

    }



    @Override

    protected void rotateCamera(float value) {super.rotateCamera(sensitivity
    value);}

    @Override

    protected void vRotateCamera(float value) {super.vRotateCamera(sensitivity*value);}

    public void setCameraSensitivity(float sensitivity){this.sensitivity=sensitivity;}



    }

I’ve made one method but it’s still in progress:

[java] private void calculatePhysic() {

CollisionResults results = new CollisionResults();

Quaternion quat180 = new Quaternion();

quat180.fromAngleAxis(FastMath.PI, camera.getUp());

Vector3f inverseCam = quat180.mult(camera.getDirection());

Ray ray = new Ray(chara.getPhysicsLocation().add(new Vector3f(0,10,0)), inverseCam);

collides.collideWith(ray, results);

if (results.size() > 0) {

if(results.getClosestCollision().getDistance() < 100){ // 100 is the max distance

distance = (int) results.getClosestCollision().getDistance();

}

else{

distance = 100;

}

}

}[/java]

It’s work well. But as you said the ray cast need to be used only when it’s necessary now.



Edit : thanks Bonechilla for the comment !

[java]private void calculatePhysic() {

Vector3f tempLocation = chara.getPhysicsLocation().add(new Vector3f(0,10,0));

CollisionResults leftRayResults = new CollisionResults();

CollisionResults rightRayResults = new CollisionResults();

CollisionResults results = new CollisionResults();

Quaternion quat180 = new Quaternion();

quat180.fromAngleAxis(FastMath.PI, camera.getUp());

Vector3f inverseCam = quat180.mult(camera.getDirection());

Ray leftRay = new Ray(tempLocation.add(camera.getLeft()), inverseCam);

Vector3f tempRight = quat180.mult(camera.getLeft());

Ray rightRay = new Ray(tempLocation.add(tempRight), inverseCam);

Ray centerRay = new Ray(chara.getPhysicsLocation().add(new Vector3f(0,10,0)), inverseCam);

collides.collideWith(centerRay, results);

collides.collideWith(leftRay, leftRayResults);

collides.collideWith(rightRay, rightRayResults);

if(leftRayResults.size() > 0 && rightRayResults.size() > 0){

if (results.size() > 0) {



if(results.getClosestCollision().getDistance() < 100){

distance = (int) results.getClosestCollision().getDistance();

}

else{

distance = 100;

}

}

}

else if(rightRayResults.size() > 0){

rotation += FastMath.PI/100;

}

else if(leftRayResults.size() > 0){

rotation -= FastMath.PI/100;

}

}[/java]

The last version. It’s working perfectly but need some smooth (quaternion slerp) i will improve this next time. If someone wants my testcase i can post it.

Yeah, me first pro …

The main :



[java]package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.bounding.BoundingBox;

import com.jme3.bullet.BulletAppState;

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

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

import com.jme3.bullet.control.RigidBodyControl;

import com.jme3.bullet.util.CollisionShapeFactory;

import com.jme3.font.BitmapFont;

import com.jme3.font.BitmapText;

import com.jme3.font.Rectangle;

import com.jme3.light.AmbientLight;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Vector3f;

import com.jme3.renderer.Camera;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.CameraNode;

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.Line;

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 jme3tools.converters.ImageToAwt;



/**

  • test
  • @author normenhansen

    /

    public class Main extends SimpleApplication {



    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }

    private CharacterGame chara;

    private BulletAppState bulletAppState;

    private CameraGame camG;

    private RigidBodyControl landscape;

    private String txtB;

    private BitmapText txt;

    private RigidBodyControl landscape2;

    private Material matRock;

    private Material matWire;

    private TerrainQuad terrain;

    private Geometry rayGeom;

    private Line ray;



    @Override

    public void simpleInitApp() {



    setUpLight();

    bulletAppState = new BulletAppState();

    stateManager.attach(bulletAppState);

    flyCam.setMoveSpeed(100);

    inputManager.removeListener(flyCam);

    //terrain

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



    // ALPHA map (for splat textures)

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



    // HEIGHTMAP image (for the terrain heightmap)

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



    // GRASS texture

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

    grass.setWrap(WrapMode.Repeat);

    matRock.setTexture(“m_Tex1”, grass);

    matRock.setFloat(“m_Tex1Scale”, 64f);



    // DIRT texture

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

    dirt.setWrap(WrapMode.Repeat);

    matRock.setTexture(“m_Tex2”, dirt);

    matRock.setFloat(“m_Tex2Scale”, 32f);



    // ROCK texture

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

    rock.setWrap(WrapMode.Repeat);

    matRock.setTexture(“m_Tex3”, rock);

    matRock.setFloat(“m_Tex3Scale”, 128f);



    // WIREFRAME material

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

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





    // CREATE HEIGHTMAP

    AbstractHeightMap heightmap = null;

    try {

    //heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);



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

    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.setLocalTranslation(0, -100, 0);

    terrain.setLocalScale(4f, 2f, 4f);

    rootNode.attachChild(terrain);

    //end

    CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(5.0f, 0.5f, 1);

    Node ninja = (Node) assetManager.loadModel(“Models/Ninja/Ninja.mesh.xml”);

    chara = new CharacterGame(ninja, capsuleShape, cam);

    chara.registerWithInput(inputManager);

    ninja.getChild(0).setLocalScale(0.6f);

    ninja.getChild(1).setLocalScale(0.6f);

    chara.setFallSpeed(100f);

    ninja.addControl(chara);

    ninja.setLocalTranslation(0, 0, 0);

    ninja.setLocalScale(0.1f);

    CollisionShape sceneShape =

    CollisionShapeFactory.createMeshShape(terrain);



    landscape = new RigidBodyControl(sceneShape, 0);

    landscape.setSpatial(terrain);

    terrain.addControl(landscape);

    rootNode.attachChild(terrain);

    camG = new CameraGame(cam, chara);

    camG.registerWithInput(inputManager);



    bulletAppState.getPhysicsSpace().add(chara);

    bulletAppState.getPhysicsSpace().add(terrain);



    camG.addCollideObject(terrain);



    rootNode.attachChild(camG.getCollides());



    rootNode.attachChild(ninja);



    rootNode.attachChild(SkyFactory.createSky(

    assetManager, “Textures/Sky/Bright/BrightSky.dds”, false));



    }



    private void setUpLight() {

    // We add light so we see the scene

    AmbientLight al = new AmbientLight();

    al.setColor(ColorRGBA.Yellow.mult(1.3f));

    rootNode.addLight(al);



    DirectionalLight dl = new DirectionalLight();

    dl.setColor(ColorRGBA.White);

    dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());

    rootNode.addLight(dl);

    initGui();

    }



    public void initGui(){



    txtB = “Null”;

    BitmapFont fnt = assetManager.loadFont(“Interface/Fonts/Default.fnt”);

    txt = new BitmapText(fnt, false);

    txt.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight()));

    txt.setSize(fnt.getPreferredSize() * 2f);

    txt.setText(txtB);

    txt.setLocalTranslation(0, settings.getHeight(), 0);

    guiNode.attachChild(txt);



    }



    @Override

    public void simpleUpdate(float tpf) {



    camG.update(tpf);

    chara.updateChara(tpf);

    txt.setText(camG.getString());



    }



    @Override

    public void simpleRender(RenderManager rm) {

    //TODO: add render code

    }

    }

    [/java]



    CharacterGame :



    [java]package mygame;



    import com.jme3.animation.AnimChannel;

    import com.jme3.animation.AnimControl;

    import com.jme3.animation.AnimEventListener;

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

    import com.jme3.bullet.control.CharacterControl;

    import com.jme3.input.InputManager;

    import com.jme3.input.KeyInput;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.AnalogListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.Spatial;



    public class CharacterGame extends CharacterControl implements ActionListener, AnalogListener, AnimEventListener {



    protected Vector3f direction = new Vector3f(0, 0, 0);

    private Camera cam;

    public Spatial chara;

    private AnimControl control;

    private AnimChannel channel;

    protected Vector3f walkTemp = new Vector3f(0, 0, 0);

    private boolean up;

    private boolean left;

    private boolean down;

    private boolean right;

    private boolean marcher;

    private String text;

    private float angleTemp = -1;

    private float angleTemp2 = 0;

    private boolean caca;

    private boolean jump;

    private boolean focus = false;



    public CharacterGame(Spatial chara, CollisionShape colShape, Camera cam) {



    super(colShape, 0.05f);

    this.cam = cam;

    this.chara = chara;

    this.setSpatial(chara);

    this.setPhysicsLocation(chara.getLocalTranslation());

    this.setJumpSpeed(20);

    this.setFallSpeed(20);

    this.setGravity(20);

    control = chara.getControl(AnimControl.class);

    control.addListener(this);

    channel = control.createChannel();

    for (String anim : control.getAnimationNames()) {

    System.out.println(anim);

    }

    channel.setAnim(“Walk”);



    }



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



    if (name.equals(“Char_Left”) && isPressed) {



    left = true;



    }

    if (name.equals(“Char_Up”) && isPressed) {



    up = true;



    }

    if (name.equals(“Char_Down”) && isPressed) {



    down = true;



    }

    if (name.equals(“Char_Right”) && isPressed) {



    right = true;



    }

    if (name.equals(“Char_Left”) && !isPressed) {



    left = false;



    }

    if (name.equals(“Char_Up”) && !isPressed) {



    up = false;



    }

    if (name.equals(“Char_Down”) && !isPressed) {



    down = false;



    }

    if (name.equals(“Char_Right”) && !isPressed) {



    right = false;



    }



    }



    public void registerWithInput(InputManager inputManager) {



    String[] mappings = new String[]{

    “Char_Left”,

    “Char_Right”,

    “Char_Up”,

    “Char_Down”,

    “Char_Jump”

    };



    inputManager.addMapping(“Char_Left”, new KeyTrigger(KeyInput.KEY_Q));

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

    inputManager.addMapping(“Char_Up”, new KeyTrigger(KeyInput.KEY_Z));

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

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

    inputManager.addListener(this, mappings);

    inputManager.setCursorVisible(false);



    }



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

    }



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

    }



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

    }



    public void updateChara(float tpf) {



    walkTemp.set(0, 0, 0);

    tpf = 1000;



    if (up && !left && !down && !right) {



    walkTemp.set(cam.getDirection().multLocal(-1, 0, -1));

    direction.set(walkTemp);



    } else if (left && !up && !down && !right) {



    walkTemp.set(cam.getLeft().multLocal(-1, 0, -1));

    direction.set(walkTemp);



    } else if (down && !left && !up && !right) {



    walkTemp.set(cam.getDirection().multLocal(1, 0, 1));

    direction.set(walkTemp);



    } else if (right && !up && !left && !down) {



    walkTemp.set(cam.getLeft().multLocal(1, 0, 1));

    direction.set(walkTemp);



    } else if (right && up && !left && !down) {



    Vector3f temp = cam.getDirection().multLocal(1, 0, 1);

    walkTemp.set(rotateVector(temp, (3 * FastMath.PI) / 4));

    direction.set(walkTemp);



    } else if (!right && up && left && !down) {



    Vector3f temp = cam.getDirection().multLocal(1, 0, 1);

    walkTemp.set(rotateVector(temp, -(3 * FastMath.PI) / 4));

    direction.set(walkTemp);



    } else if (right && !up && !left && down) {



    Vector3f temp = cam.getDirection().multLocal(1, 0, 1);

    walkTemp.set(rotateVector(temp, FastMath.PI / 4));

    direction.set(walkTemp);



    } else if (!right && !up && left && down) {



    Vector3f temp = cam.getDirection().multLocal(1, 0, 1);

    walkTemp.set(rotateVector(temp, -FastMath.PI / 4));

    direction.set(walkTemp);



    }

    if(!right && !up && !down && !left){



    if(!channel.getAnimationName().equals(“Idle3”)){



    channel.setAnim(“Idle3”, 0.5f);



    }





    }

    else{



    if(channel.getAnimationName().equals(“Idle3”)){



    channel.setAnim(“Walk” , 0.5f);



    }



    }

    if(right || up || down || left){



    updateViewDirection(tpf);



    }



    this.setWalkDirection(walkTemp.multLocal(-1, 0, -1));



    }



    private void updateViewDirection(float tpf) {



    Quaternion increment1 = new Quaternion();

    increment1.fromAngles(direction.x, 0, direction.z);

    Quaternion increment2 = new Quaternion();

    increment2.fromAngles(this.getViewDirection().x, 0, this.getViewDirection().z);

    Quaternion increment3 = increment2.slerp(increment1, increment2, 0.96f);

    float[] tempAngle = {0, 0, 0};

    increment3.toAngles(tempAngle);

    Vector3f finalDirection = new Vector3f(tempAngle[0], 0, tempAngle[2]);

    this.setViewDirection(finalDirection);

    this.setWalkDirection(finalDirection);



    }



    public String getString() {



    return text;



    }



    public int getOrientation(float angle1, float angle2) {



    float a = angle1 - angle2;

    if (a > 0) {

    return 1;

    } else {

    return -1;

    }



    }



    public Vector3f rotateVector(Vector3f vector, float angle) {



    Quaternion increment = new Quaternion();

    increment.fromAngleAxis(angle, Vector3f.UNIT_Y);

    return increment.mult(vector);



    }

    }

    [/java]



    CameraGame :



    [java]package mygame;



    import com.jme3.asset.AssetManager;

    import com.jme3.collision.CollisionResults;

    import com.jme3.input.InputManager;

    import com.jme3.input.KeyInput;

    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.math.FastMath;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Ray;

    import com.jme3.math.Vector2f;

    import com.jme3.math.Vector3f;

    import com.jme3.renderer.Camera;

    import com.jme3.scene.Node;

    import com.jme3.scene.Spatial;



    public class CameraGame implements AnalogListener, ActionListener {



    private Camera camera;

    private CharacterGame chara;

    private Vector3f initialUpVec;

    private InputManager inputManager;

    private int distance = 75;

    private int timer = 0;

    private Vector3f tempCam = new Vector3f(0,0,0);

    private boolean increment = false;

    private boolean sense = false;

    private float vRotation = FastMath.PI/8;

    public float rotation = FastMath.PI/2;

    private String text = “ahahahah”;

    private boolean focus = false;

    private Node collides = new Node();

    private Vector3f finalVector = new Vector3f(0,0,0);

    private Quaternion tempIncrement = new Quaternion();

    private boolean stop;

    private boolean inRotation = false;

    private float gauche = 0;

    private float droit = 0;

    public CameraGame(Camera cam, CharacterGame character) {



    camera = cam;

    camera.setLocation(new Vector3f(100,50,100));

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

    chara = character;



    }



    public void addCollideObject(Spatial obj){



    collides.attachChild(obj);



    }



    public Node getCollides(){



    return collides;



    }



    public void registerWithInput(InputManager inputManager) {



    this.inputManager = inputManager;



    String[] mappings = new String[]{

    “Cam_Left”,

    “Cam_Right”,

    “Cam_Up”,

    “Cam_Down”,

    “Cam_Focus”

    };



    inputManager.addMapping(“Cam_Left”, new MouseAxisTrigger(0, true));

    inputManager.addMapping(“Cam_Right”, new MouseAxisTrigger(0, false));

    inputManager.addMapping(“Cam_Up”, new MouseAxisTrigger(1, false));

    inputManager.addMapping(“Cam_Down”, new MouseAxisTrigger(1, true));

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

    inputManager.addListener(this, mappings);

    inputManager.setCursorVisible(false);



    }



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







    }



    public boolean getLook(){



    return focus;



    }



    public Camera getCamera(){



    return camera;



    }



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



    if (name.equals(“Cam_Focus”) && isPressed) {



    focus = true;

    finalVector = new Vector3f(chara.getViewDirection().x, 0, chara.getViewDirection().z);



    }

    if (name.equals(“Cam_Focus”) && !isPressed) {



    focus = false;



    }





    }



    public void update(float tpf) {



    calculatePhysic();

    Vector3f charaV = chara.walkTemp.normalize();

    Vector3f camerV = camera.getDirection().normalize();

    Vector2f chara2D = new Vector2f(charaV.x, charaV.z);

    Vector2f camer2D = new Vector2f(camerV.x, camerV.z);

    float angle = chara2D.angleBetween(camer2D);

    angle = normalize(angle);

    Vector3f charaV2 = chara.getPhysicsLocation().clone();

    Vector3f camerV2 = camera.getLocation().clone();

    Vector2f direction = new Vector2f(camerV2.x- charaV2.x , camerV2.z - charaV2.z);

    float angle2 = direction.getAngle();

    if (focus) {



    Quaternion increment1 = new Quaternion();

    increment1.fromAngles(finalVector.x, 0, finalVector.z);

    Quaternion increment2 = new Quaternion();

    float a = FastMath.asin(camera.getDirection().x);

    a += FastMath.PI;

    a = FastMath.sin(a);

    float b = FastMath.acos(camera.getDirection().z);

    b += FastMath.PI;

    b = FastMath.cos(b);

    increment2.fromAngles(a, 0, b);

    Quaternion increment3 = increment2.slerp(increment1, increment2, 0.96f);

    float[] tempAngle = {0, 0, 0};

    increment3.toAngles(tempAngle);

    Vector2f finalDirection = new Vector2f(tempAngle[0], tempAngle[2]);

    rotation = finalDirection.getAngle();

    if (tempIncrement.equals(increment3)) {



    focus = true;



    }

    tempIncrement.set(increment3);



    }

    if (((angle > -1.58 && angle < -1.56) || (angle < 1.58 && angle > 1.56)) && !focus) {



    rotation = angle2;

    //test

    float hDistance = distance * FastMath.sin((FastMath.PI / 2) - vRotation);

    Vector3f pos = new Vector3f(hDistance * FastMath.cos(rotation), distance * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));

    pos = pos.add(chara.getPhysicsLocation());

    camera.setLocation(pos);

    //end test



    } else {



    float hDistance = distance * FastMath.sin((FastMath.PI / 2) - vRotation);

    Vector3f pos = new Vector3f(hDistance * FastMath.cos(rotation), distance * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));

    pos = pos.add(chara.getPhysicsLocation());

    camera.setLocation(pos);



    }



    camera.lookAt(chara.getPhysicsLocation().add(new Vector3f(0,10,0)), Vector3f.UNIT_Y);



    }



    public String getString(){



    return text;



    }



    private float normalize(float angle) {



    if(angle > FastMath.PI){



    angle -= 2
    FastMath.PI;



    }

    else if(angle < -FastMath.PI){



    angle += 2
    FastMath.PI;



    }

    return angle;

    }



    private void calculatePhysic() {



    Vector3f tempLocation = chara.getPhysicsLocation().add(new Vector3f(0,10,0));



    CollisionResults leftRayResults = new CollisionResults();

    CollisionResults rightRayResults = new CollisionResults();

    CollisionResults results = new CollisionResults();



    Quaternion quat180 = new Quaternion();

    quat180.fromAngleAxis(FastMath.PI, camera.getUp());

    Vector3f inverseCam = quat180.mult(camera.getDirection());





    Ray leftRay = new Ray(tempLocation.add(camera.getLeft()), inverseCam);

    Vector3f tempRight = quat180.mult(camera.getLeft());

    Ray rightRay = new Ray(tempLocation.add(tempRight), inverseCam);

    Ray centerRay = new Ray(chara.getPhysicsLocation().add(new Vector3f(0,10,0)), inverseCam);

    collides.collideWith(centerRay, results);

    collides.collideWith(leftRay, leftRayResults);

    collides.collideWith(rightRay, rightRayResults);

    if(leftRayResults.size() > 0 && rightRayResults.size() > 0){



    if (results.size() > 0) {



    text = String.valueOf(results.getClosestCollision().getDistance());

    if(results.getClosestCollision().getDistance() < 100){



    distance = (int) results.getClosestCollision().getDistance();



    }

    else{



    distance = 100;



    }



    }



    }

    else if(rightRayResults.size() > 0){



    rotation += FastMath.PI/100;



    }

    else if(leftRayResults.size() > 0){



    rotation -= FastMath.PI/100;



    }



    }



    }



    [/java]



    NOT PRO ! It’s ugly and just to test. Moreover it’s not prefect ^^. I’ve want you to improve my camera :p.

Sorry that I reply so late…



I’m trying to make a proper test case for code… It’s an in-game scene with a lot of “annoying obstacles”.



Haha, First when I try it, you messed up the input trigger man ! :stuck_out_tongue:



Second, the physics work not very good and it shake like crazy when across a wall <= this is what we intend to deal with by physic smooth method .:stuck_out_tongue:



Please give me some time and i try post a zip of my code for you to test it out… Cheer !