package org.pollux3d.cam;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
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.Spatial;
import com.jme3.scene.control.Control;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A camera that follows a spatial and can turn around it by dragging the mouse
* @author nehon
*/
public class PolluxCam implements Control, Cam{
private Spatial target = null;
private float distance = 180;
private float minHeight = -(FastMath.PI / 2 - 0.0001f);
private float maxHeight = (FastMath.PI / 2 - 0.0001f);
private float minDistance = 10.0f;
private float maxDistance = 10000.0f;
private float zoomSpeed = 0.1f;
private boolean canZoom = true;
private float rotationSpeed = 0.01f;
private Quaternion rotaionOffset = new Quaternion();
/**
* the camera.
*/
private Camera cam = null;
private Vector3f initialUpVec;
private boolean canRotate = true;
private float hRotation = FastMath.PI / 2;
private float vRotation = 0;
private boolean enabled = true;
private List<CamLocationListener> camDirectionListeners = new ArrayList<CamLocationListener>();
/**
* Constructs the chase camera
* @param cam the application camera
* @param target the spatial to follow
*/
public PolluxCam(Camera cam, final Spatial target) {
this.target = target;
target.addControl(this);
this.cam = cam;
cam.setFrustumFar(10000000f);
initialUpVec = cam.getUp().clone();
//this.setHorizontalOffset(-0.2f);
}
public Vector3f getDirection() {
return cam.getDirection();
}
public float getZoomDistance() {
return distance;
}
public void setZoomDistance(float distance) {
this.distance = distance;
}
public void setCanZoom(boolean canZoom) {
this.canZoom = canZoom;
}
public Ray getClickRay(Vector2f point) {
Vector3f camLocation = cam.getLocation();
Vector3f clickPoint = cam.getWorldCoordinates(point, 0.9f);
Vector3f dir = clickPoint.subtract(camLocation).normalize();
return new Ray(camLocation, dir);
//return new Ray(camLocation, cam.getDirection());
}
//rotate the camera around the target on the horizontal plane
public void hRotateCamera(float value) {
if (!canRotate || !enabled) {
return;
}
hRotation += value * rotationSpeed;
//updateCamera();
}
//move the camera toward or away the target
public void zoomCamera(float value) {
if (!enabled || !canZoom) {
return;
}
distance += value * zoomSpeed;
if (distance > maxDistance) {
distance = maxDistance;
}
if (distance < minDistance) {
distance = minDistance;
}
if ((vRotation < minHeight) && (distance > (minDistance + 1.0f))) {
vRotation = minHeight;
}
//updateCamera();
}
//rotate the camera around the target on the vertical plane
public void vRotateCamera(float value) {
if (!canRotate || !enabled) {
return;
}
vRotation += value * rotationSpeed;
if (vRotation > maxHeight) {
vRotation = maxHeight;
}
if ((vRotation < minHeight) && (distance > (minDistance + 1.0f))) {
vRotation = minHeight;
}
//updateCamera();
}
/**
* Update the camera, should only be called internally
*/
public void updateCamera() {
float hDistance = distance * FastMath.sin((FastMath.PI / 2) - vRotation);
Vector3f pos = new Vector3f(hDistance * FastMath.cos(hRotation), distance * FastMath.sin(vRotation), hDistance * FastMath.sin(hRotation));
pos = pos.add(target.getLocalTranslation());
cam.setLocation(pos);
cam.lookAt(target.getLocalTranslation(), initialUpVec);
cam.setRotation(cam.getRotation().mult(rotaionOffset));
cam.updateViewProjection();
}
public void updateListeners() {
for (CamLocationListener listener : camDirectionListeners) {
listener.onCamLocationChange(cam.getLocation());
}
}
public void setRotationOffset(float vertical, float horizontal) {
rotaionOffset = new Quaternion().fromAngles(vertical, horizontal, 0);;
}
/**
* 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) {
PolluxCam pc = new PolluxCam(cam, spatial);
pc.setMaxDistance(getMaxDistance());
pc.setMinDistance(getMinDistance());
return pc;
}
/**
* 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();
}
/**
* renders the camera control, should on ly be used internally
* @param rm
* @param vp
*/
public void render(RenderManager rm, ViewPort vp) {
//nothing to render (in Control interface but we use only update)
}
/**
* 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);
}
public void addCamDirectionListener(CamLocationListener listener) {
if (listener == null) throw new NullPointerException();
camDirectionListeners.add(listener);
}
public void removeAllCamDirectionListener() {
camDirectionListeners.clear();
}
public void removeCamDirectionListener(CamLocationListener listener) {
if (listener == null) throw new NullPointerException();
camDirectionListeners.remove(listener);
}
}