Here is an example of a camera I made a long time ago.
import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.collision.CollisionResults;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.input.InputManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
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.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Box;
import com.jme3.util.TempVars;
import java.io.IOException;
/**
* @deprecated
* @author Stomrage
*/
public class ZeldaCam implements Control, RawInputListener {
//The target of your camera
private Spatial target;
private Camera cam;
//The minimum distance that the camera can reach
private final float minDistance = 20;
//The maximum distance that the camera can reach
private final float maxDistance = 50;
//The distance the camera has to reach
private float desiredDistance = 30;
//It's the size of the target
private float minHauteur = 0;
//The distance that the character need to reach when there is to indicate
//the distance he has to reach
private float perfectDistance = 30;
//Needed for the camera.lookat
private final Vector3f initialUpVec;
//The node used for camera collision
private Node collides;
//The stick current x rotation
private float xRotation;
//The stick current y rotation
private float yRotation;
//The current rotation speed for the interpolateLinear more greater this
//value is more smoother the rotation will be
private float roationSpeed = 2;
private int borderSize = 30;
private int borderIncrement = 25;
private boolean lockedCamera = false;
//Indicate the current distance for the stick value
private float stickDistance = 0;
private Node topBorder = new Node();
private Node botBorder = new Node();
private Vector3f topDefaultPosition = new Vector3f(0, 0, 0);
private Vector3f botDefaultPosition = new Vector3f(0, 0, 0);
private Vector3f finalTopLocation = new Vector3f(0, 0, 0);
private Vector3f finalBotLocation = new Vector3f(0, 0, 0);
private Spatial locked;
private Vector3f finalTarget = new Vector3f(0,0,0);
private Vector3f rotation = new Vector3f(0,0,0);
private Vector3f targetLastPosition = new Vector3f(0,0,0);
public ZeldaCam(Camera cam, Spatial target) {
this.cam = cam;
this.target = target;
this.target.addControl(this);
initialUpVec = cam.getUp().clone();
minHauteur = ((BoundingBox) target.getWorldBound()).getYExtent() * 2;
targetLastPosition.set(target.getLocalTranslation());
}
public void initiateBorder(Node guiNode, AssetManager assetManager, int screenWidth, int screenHeight) {
borderIncrement = screenHeight / borderIncrement;
Box b = new Box(new Vector3f(0, (screenHeight / borderSize), 0), screenWidth, screenHeight / borderSize + borderIncrement, 1);
Geometry geom = new Geometry("Box", b);
Material mat = new Material(assetManager,
"Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
geom.setMaterial(mat);
topBorder.attachChild(geom);
b = new Box(new Vector3f(0, screenHeight - (screenHeight / borderSize), 0), screenWidth, screenHeight / borderSize + borderIncrement, 1);
geom = new Geometry("Box", b);
mat = new Material(assetManager,
"Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
geom.setMaterial(mat);
botBorder.attachChild(geom);
guiNode.attachChild(topBorder);
guiNode.attachChild(botBorder);
topDefaultPosition.set(topBorder.getLocalTranslation());
botDefaultPosition.set(botBorder.getLocalTranslation());
finalTopLocation.set(topBorder.getLocalTranslation());
finalBotLocation.set(botBorder.getLocalTranslation());
}
public void initKey(InputManager inputManager) {
inputManager.addRawInputListener(this);
}
public Control cloneForSpatial(Spatial spatial) {
ZeldaCam cc = new ZeldaCam(cam, spatial);
return cc;
}
public void setSpatial(Spatial spatial) {
this.target = spatial;
minHauteur = ((BoundingBox) target.getWorldBound()).getYExtent() * 2;
}
public void update(float tpf) {
updateCamera(tpf);
}
public void render(RenderManager rm, ViewPort vp) {
}
public void write(JmeExporter ex) throws IOException {
}
public void read(JmeImporter im) throws IOException {
}
private void updateCamera(float tpf) {
computePosition(tpf);
}
public void setLockedSpatial(Spatial spatial) {
this.locked = spatial;
}
private void computePosition(float tpf) {
/*The stick distance is calculated here depending on the stick rotation given by yRotation this value is reached by the maxDistance and minDistance
*so you can zoom between the max and min range.
*/
stickDistance = Math.max(minDistance - perfectDistance, Math.min(maxDistance - perfectDistance, stickDistance + yRotation * tpf * 40));
finalTarget.set(Vector3f.ZERO);
Vector3f positionRectification = new Vector3f(0,0,0);
if(lockedCamera){
if(locked!=null){
Vector3f tempVector = target.getLocalTranslation().clone().addLocal(0,minHauteur/2,0);
tempVector.addLocal(locked.getLocalTranslation());
finalTarget = tempVector.divide(2);
Vector3f tempDirection = locked.getLocalTranslation().subtract(target.getLocalTranslation());
tempDirection.negateLocal();
Quaternion tempQuat = new Quaternion();
tempQuat.fromAngles(0, FastMath.atan2(tempDirection.getX(), tempDirection.getZ()), 0);
target.setLocalRotation(tempQuat);
}else{
finalTarget = target.getLocalTranslation().clone();
finalTarget.addLocal(0,minHauteur/2,0);
positionRectification = target.getLocalTranslation().subtract(targetLastPosition);
}
}else{
finalTarget = target.getLocalTranslation().clone();
finalTarget.addLocal(0,minHauteur/2,0);
positionRectification = target.getLocalTranslation().subtract(targetLastPosition);
}
//Calculate the height between the cam and the "top" of the target
float hauteurCam = cam.getLocation().getY()-finalTarget.getY();
//We copy the camera location into a temp variable
Vector3f finalLocation = cam.getLocation().clone();
//Substract the current distance of the cam with the character height given by minHauteur
finalLocation.subtractLocal(new Vector3f(0f, hauteurCam - FastMath.exp(desiredDistance / 20), 0f));
//Make sure there is something to collide
if (collides != null) {
//If something collide then true or false if there is nothing in the eyes of the camera
if (!cameraCollision(finalTarget)) {
//If nothing collide in the path of the camera eyes it's ok we can reach the perfect distance (depending on the stick distance)
desiredDistance = Math.max(minDistance, Math.min(maxDistance, perfectDistance + stickDistance));
//Calculate the current distance between the target and the camera
float currentDistance = cam.getLocation().distance(finalTarget);
//currentDistance = Math.min(maxDistance, currentDistance);
//We reach the desired distance and make sure to substract the currentDistance
if(lockedCamera){
if(locked!=null){
finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));
}else{
Vector3f tempDirection = cam.getDirection().add(rotation);
tempDirection.multLocal(50);
finalLocation.addLocal(tempDirection);
finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));
}
} else {
finalLocation.addLocal(cam.getDirection().mult(currentDistance - desiredDistance));
}
} else {
if (lockedCamera) {
if (locked == null) {
Vector3f tempDirection = cam.getDirection().add(rotation);
tempDirection.multLocal(50);
finalLocation.addLocal(tempDirection);
}
}
}
}
//Rotate the camera left or right depending on the stick rotation
finalLocation.addLocal(cam.getLeft().mult(xRotation * roationSpeed * desiredDistance / 5));
Vector3f interpolateDistance = FastMath.interpolateLinear(tpf * 6, cam.getLocation().clone(), finalLocation);
interpolateDistance.addLocal(positionRectification);
//Change the camera location
cam.setLocation(interpolateDistance);
//Look at the head of the character
cam.lookAt(finalTarget, initialUpVec);
Vector3f interpolateTopLocation = FastMath.interpolateLinear(tpf * 6, topBorder.getLocalTranslation(), finalTopLocation);
Vector3f interpolateBotLocation = FastMath.interpolateLinear(tpf * 6, botBorder.getLocalTranslation(), finalBotLocation);
topBorder.setLocalTranslation(interpolateTopLocation);
botBorder.setLocalTranslation(interpolateBotLocation);
targetLastPosition.set(target.getLocalTranslation());
}
private boolean cameraCollision(Vector3f finalTarget) {
//Here we rotate the camera direction to cast a ray and detect if something is in the eyes of the camera
Vector3f direction = cam.clone().getDirection();
Quaternion quat180 = new Quaternion();
quat180.fromAngleAxis(FastMath.PI, cam.getUp());
Vector3f inverseCamDirection = quat180.mult(direction);
Ray ray = new Ray(finalTarget, inverseCamDirection);
CollisionResults results = new CollisionResults();
collides.collideWith(ray, results);
if (results.size() > 0) {
float resultDistance = results.getClosestCollision().getDistance();
//If there is something we make sure it's not behind the camera max range
if (resultDistance < maxDistance) {
//If the resultDistance is greater than the desiredDistance+stickDistance there is no collision
if (resultDistance < perfectDistance + stickDistance) {
//If there is really a collision we can set the desired distance to the resultDistance
//All the other case mean that there is no collision so we return false
float currentDistance = cam.getLocation().distance(finalTarget);
cam.getLocation().addLocal(cam.getDirection().clone().mult(currentDistance - resultDistance));
desiredDistance = resultDistance;
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
void setCollidesNode(Node collides) {
this.collides = collides;
}
public void onJoyAxisEvent(JoyAxisEvent evt) {
float value = evt.getValue();
String joystickName = evt.getAxis().getName();
if (joystickName.contains("Rotation")) {
if (joystickName.contains("X")) {
if (FastMath.abs(value) < 0.35) {
xRotation = 0;
} else {
xRotation = value;
}
} else if (joystickName.contains("Y")) {
if (FastMath.abs(value) < 0.25) {
yRotation = 0;
} else {
yRotation = value;
}
}
}
}
public void onJoyButtonEvent(JoyButtonEvent evt) {
if (evt.getButtonIndex() == 5) {
lockedCamera = !lockedCamera;
if (lockedCamera) {
finalTopLocation.set(topDefaultPosition.add(0, borderIncrement, 0));
finalBotLocation.set(botDefaultPosition.add(0, -borderIncrement, 0));
rotation = target.getLocalRotation().getRotationColumn(2);
} else {
finalTopLocation.set(topDefaultPosition);
finalBotLocation.set(botDefaultPosition);
rotation.set(new Vector3f(0,0,0));
}
}else if(evt.getButtonIndex() == 4){
locked = null;
}
}
public void onMouseMotionEvent(MouseMotionEvent evt) {
}
public void onMouseButtonEvent(MouseButtonEvent evt) {
}
public void onKeyEvent(KeyInputEvent evt) {
}
public void onTouchEvent(TouchEvent evt) {
}
public void beginInput() {
}
public void endInput() {
}
}
The video where I use this system. Hope you find it usefull. (I’m using joystick for input)