Created
December 10, 2012 18:44
-
-
Save erlend-sh/4252435 to your computer and use it in GitHub Desktop.
jMonkeyEngine: IT IS: 1. a copy of the current ChaseCam class (state 11th Jan 2011) 2. extended, to make the cam zoom in, once it (or the line of sight between cam and target) collides with objects 3. zooming out to initial zoom distance, once the obstacle is gone 4. still taking orders from zoom controlling events like mouse wheel rotation, the…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.jme3.bullet.PhysicsSpace; | |
import com.jme3.bullet.collision.PhysicsRayTestResult; | |
import com.jme3.bullet.nodes.PhysicsNode; | |
import com.jme3.export.InputCapsule; | |
import com.jme3.export.JmeExporter; | |
import com.jme3.export.JmeImporter; | |
import com.jme3.export.OutputCapsule; | |
import com.jme3.input.*; | |
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.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; | |
/** | |
* this is an extended version of ChaseCamera. | |
* @see ChaseCamera for original | |
* @author Mark | |
*/ | |
public class PhysicalChaseCamera implements ActionListener, AnalogListener, Control { | |
private Spatial target = null; | |
private float minVerticalRotation = 0.00f; | |
private float maxVerticalRotation = FastMath.PI / 2; | |
private float minDistance = 1.0f; | |
private float maxDistance = 40.0f; | |
private float maxDistanceByZoom = 20.0f; | |
private float distance = 20; | |
private float zoomSpeed = 2f; | |
private float rotationSpeed = 1.0f; | |
private float rotation = 0; | |
private float trailingRotationInertia = 0.05f; | |
private float zoomSensitivity = 5f; | |
private float rotationSensitivity = 5f; | |
private float chasingSensitivity = 5f; | |
private float trailingSensitivity = 0.5f; | |
private float vRotation = FastMath.PI / 6; | |
private boolean smoothMotion = false; | |
private boolean trailingEnabled = true; | |
private float rotationLerpFactor = 0; | |
private float trailingLerpFactor = 0; | |
private boolean rotating = false; | |
private boolean vRotating = false; | |
private float targetRotation = rotation; | |
private InputManager inputManager; | |
private Vector3f initialUpVec; | |
private float targetVRotation = vRotation; | |
private float vRotationLerpFactor = 0; | |
private float targetDistance = distance; | |
private float distanceLerpFactor = 0; | |
private boolean zooming = false; | |
private boolean trailing = false; | |
private boolean chasing = false; | |
private boolean canRotate; | |
private float offsetDistance = 0.002f; | |
private Vector3f prevPos; | |
private boolean targetMoves = false; | |
private boolean enabled = true; | |
private Camera cam = null; | |
private Vector3f targetDir; | |
private float previousTargetRotation; | |
private Vector3f pos; | |
private Vector3f maxPos = new Vector3f(); | |
private PhysicsSpace myPhysicsSpace; | |
boolean zoomin; | |
/** | |
* Constructs the physical chase camera, registers inputs and physics | |
* @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, the cam shall be active in | |
*/ | |
public PhysicalChaseCamera(Camera cam, final Spatial target, | |
InputManager inputManager, PhysicsSpace physicsSpace) { | |
//chase cam constructor stuff | |
this.target = target; | |
this.cam = cam; | |
initialUpVec = cam.getUp().clone(); | |
//add PhysicsNode to world physicsSpace | |
myPhysicsSpace = physicsSpace; | |
//chase cam stuff. | |
computePosition(); | |
target.addControl(this); | |
prevPos = new Vector3f(target.getWorldTranslation()); | |
cam.setLocation(pos); | |
registerWithInput(inputManager); | |
} | |
public void onAction(String name, boolean keyPressed, float tpf) { | |
if (name.equals("toggleRotate") && 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("mouseLeft")) { | |
rotateCamera(-value); | |
} else if (name.equals("mouseRight")) { | |
rotateCamera(value); | |
} else if (name.equals("Up")) { | |
vRotateCamera(value); | |
} else if (name.equals("Down")) { | |
vRotateCamera(-value); | |
} else if (name.equals("ZoomIn")) { | |
zoomCamera(value); | |
if (zoomin == false) { | |
distanceLerpFactor = 0; | |
} | |
zoomin = true; | |
} else if (name.equals("ZoomOut")) { | |
zoomCamera(-value); | |
if (zoomin == true) { | |
distanceLerpFactor = 0; | |
} | |
zoomin = false; | |
} | |
} | |
/** | |
* Registers inputs with the input manager | |
* @param inputManager | |
*/ | |
public void registerWithInput(InputManager inputManager) { | |
String[] inputs = {"toggleRotate", "Down", "Up", "mouseLeft", "mouseRight", "ZoomIn", "ZoomOut"}; | |
this.inputManager = inputManager; | |
inputManager.addMapping("Up", new MouseAxisTrigger(1, true)); | |
inputManager.addMapping("Down", new MouseAxisTrigger(1, false)); | |
inputManager.addMapping("ZoomIn", new MouseAxisTrigger(2, true)); | |
inputManager.addMapping("ZoomOut", new MouseAxisTrigger(2, false)); | |
inputManager.addMapping("mouseLeft", new MouseAxisTrigger(0, true)); | |
inputManager.addMapping("mouseRight", new MouseAxisTrigger(0, false)); | |
//inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); | |
inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); | |
inputManager.addListener(this, inputs); | |
} | |
private void computePosition() { | |
float hDistance = (distance) * FastMath.sin((FastMath.PI / 2) - vRotation); | |
pos = new Vector3f(hDistance * FastMath.cos(rotation), (distance) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation)); | |
pos = pos.add(target.getWorldTranslation()); | |
hDistance = (maxDistanceByZoom) * FastMath.sin((FastMath.PI / 2) - vRotation); | |
maxPos = new Vector3f(hDistance * FastMath.cos(rotation), (maxDistanceByZoom) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation)); | |
maxPos = maxPos.add(target.getWorldTranslation()); | |
this.collide(); | |
} | |
//rotate the camera around the target on the horizontal plane | |
private void rotateCamera(float value) { | |
if (!canRotate || !enabled) { | |
return; | |
} | |
rotating = true; | |
targetRotation += value * rotationSpeed; | |
} | |
//move the camera toward or away the target | |
private void zoomCamera(float value) { | |
if (!enabled) { | |
return; | |
} | |
zooming = true; | |
maxDistanceByZoom += value * zoomSpeed; | |
if (maxDistanceByZoom > maxDistance) { | |
maxDistanceByZoom = maxDistance; | |
} | |
if (maxDistanceByZoom < minDistance) { | |
maxDistanceByZoom = 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; | |
} | |
vRotating = true; | |
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) { | |
if (smoothMotion) { | |
//computation of target direction | |
targetDir = target.getWorldTranslation().subtract(prevPos); | |
float dist = targetDir.length(); | |
//Low pass filtering on the target postition to avoid shaking when physics are enabled. | |
if (offsetDistance < dist) { | |
//target moves, start chasing. | |
chasing = true; | |
//target moves, start trailing if it has to. | |
if (trailingEnabled) { | |
trailing = true; | |
} | |
//target moves... | |
targetMoves = true; | |
} else { | |
//if target was moving, we compute a slight offset in rotation to avoid a rought stop of the cam | |
//We do not if the player is rotationg the cam | |
if (targetMoves && !canRotate) { | |
if (targetRotation - rotation > trailingRotationInertia) { | |
targetRotation = rotation + trailingRotationInertia; | |
} else if (targetRotation - rotation < -trailingRotationInertia) { | |
targetRotation = rotation - trailingRotationInertia; | |
} | |
} | |
//Target stops | |
targetMoves = false; | |
} | |
//the user is rotating the cam by dragging the mouse | |
if (canRotate) { | |
//reseting the trailing lerp factor | |
trailingLerpFactor = 0; | |
//stop trailing user has the control | |
trailing = false; | |
} | |
if (trailingEnabled && trailing) { | |
if (targetMoves) { | |
//computation if the inverted direction of the target | |
Vector3f a = targetDir.negate().normalizeLocal(); | |
//the x unit vector | |
Vector3f b = Vector3f.UNIT_X; | |
//2d is good enough | |
a.y = 0; | |
//computation of the rotation angle between the x axis and the trail | |
if (targetDir.z > 0) { | |
targetRotation = FastMath.TWO_PI - FastMath.acos(a.dot(b)); | |
} else { | |
targetRotation = FastMath.acos(a.dot(b)); | |
} | |
if (targetRotation - rotation > FastMath.PI || targetRotation - rotation < -FastMath.PI) { | |
targetRotation -= FastMath.TWO_PI; | |
} | |
//if there is an important change in the direction while trailing reset of the lerp factor to avoid jumpy movements | |
if (targetRotation != previousTargetRotation && FastMath.abs(targetRotation - previousTargetRotation) > FastMath.PI / 8) { | |
trailingLerpFactor = 0; | |
} | |
previousTargetRotation = targetRotation; | |
} | |
//computing lerp factor | |
trailingLerpFactor = Math.min(trailingLerpFactor + tpf * tpf * trailingSensitivity, 1); | |
//computing rotation by linear interpolation | |
rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation); | |
//if the rotation is near the target rotation we're good, that's over | |
if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) { | |
trailing = false; | |
trailingLerpFactor = 0; | |
} | |
} | |
//linear interpolation of the distance while chasing | |
if (chasing) { | |
distance = target.getWorldTranslation().subtract(cam.getLocation()).length(); | |
distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * chasingSensitivity * 0.05f), 1); | |
distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance); | |
if (targetDistance + 0.01f >= distance && targetDistance - 0.01f <= distance) { | |
distanceLerpFactor = 0; | |
chasing = false; | |
} | |
} | |
//linear interpolation of the distance while zooming | |
if (zooming) { | |
distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * zoomSensitivity), 1); | |
distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance); | |
if (targetDistance + 0.1f >= distance && targetDistance - 0.1f <= distance) { | |
zooming = false; | |
distanceLerpFactor = 0; | |
} | |
} | |
//linear interpolation of the rotation while rotating horizontally | |
if (rotating) { | |
rotationLerpFactor = Math.min(rotationLerpFactor + tpf * tpf * rotationSensitivity, 1); | |
rotation = FastMath.interpolateLinear(rotationLerpFactor, rotation, targetRotation); | |
if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) { | |
rotating = false; | |
rotationLerpFactor = 0; | |
} | |
} | |
//linear interpolation of the rotation while rotating vertically | |
if (vRotating) { | |
vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1); | |
vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation); | |
if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) { | |
vRotating = false; | |
vRotationLerpFactor = 0; | |
} | |
} | |
//computing the position | |
computePosition(); | |
//setting the position at last | |
cam.setLocation(pos); | |
} else { | |
//easy no smooth motion | |
vRotation = targetVRotation; | |
rotation = targetRotation; | |
distance = targetDistance; | |
computePosition(); | |
cam.setLocation(pos); | |
} | |
//keeping track on the previous position of the target | |
prevPos = new Vector3f(target.getWorldTranslation()); | |
//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) { | |
ChaseCamera cc = new ChaseCamera(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; | |
} | |
/** | |
* returns true is smmoth motion is enabled for this chase camera | |
* @return | |
*/ | |
public boolean isSmoothMotion() { | |
return smoothMotion; | |
} | |
/** | |
* Enables smooth motion for this chase camera | |
* @param smoothMotion | |
*/ | |
public void setSmoothMotion(boolean smoothMotion) { | |
this.smoothMotion = smoothMotion; | |
} | |
/** | |
* returns the chasing sensitivity | |
* @return | |
*/ | |
public float getChasingSensitivity() { | |
return chasingSensitivity; | |
} | |
/** | |
* Sets the chasing sensitivity, the lower the value the slower the camera will go in the trail of the target when it moves | |
* @param chasingSensitivity | |
*/ | |
public void setChasingSensitivity(float chasingSensitivity) { | |
this.chasingSensitivity = chasingSensitivity; | |
} | |
/** | |
* Returns the rotation sensitivity | |
* @return | |
*/ | |
public float getRotationSensitivity() { | |
return rotationSensitivity; | |
} | |
/** | |
* Sets the rotation sensitivity, the lower the value the slower the camera will rotates around the target when draging with the mouse | |
* default is 5 | |
* @param rotationSensitivity | |
*/ | |
public void setRotationSensitivity(float rotationSensitivity) { | |
this.rotationSensitivity = rotationSensitivity; | |
} | |
/** | |
* returns true if the trailing is enabled | |
* @return | |
*/ | |
public boolean isTrailingEnabled() { | |
return trailingEnabled; | |
} | |
/** | |
* Enable the camera trailing : The camera smoothly go in the targets trail when it moves. | |
* @param trailingEnabled | |
*/ | |
public void setTrailingEnabled(boolean trailingEnabled) { | |
this.trailingEnabled = trailingEnabled; | |
} | |
/** | |
* returns the trailing rotation inertia | |
* @return | |
*/ | |
public float getTrailingRotationInertia() { | |
return trailingRotationInertia; | |
} | |
/** | |
* Sets the trailing rotation inertia : default is 0.1. This prevent the camera to roughtly stop when the target stops moving | |
* before the camera reached the trail position. | |
* @param trailingRotationInertia | |
*/ | |
public void setTrailingRotationInertia(float trailingRotationInertia) { | |
this.trailingRotationInertia = trailingRotationInertia; | |
} | |
/** | |
* returns the trailing sensitivity | |
* @return | |
*/ | |
public float getTrailingSensitivity() { | |
return trailingSensitivity; | |
} | |
/** | |
* Sets the trailing sensitivity, the lower the value, the slower the camera will go in the target trail when it moves. | |
* default is 0.5; | |
* @param trailingSensitivity | |
*/ | |
public void setTrailingSensitivity(float trailingSensitivity) { | |
this.trailingSensitivity = trailingSensitivity; | |
} | |
/** | |
* returns the zoom sensitivity | |
* @return | |
*/ | |
public float getZoomSensitivity() { | |
return zoomSensitivity; | |
} | |
/** | |
* Sets the zoom sensitivity, the lower the value, the slower the camera will zoom in and out. | |
* default is 5. | |
* @param zoomSensitivity | |
*/ | |
public void setZoomSensitivity(float zoomSensitivity) { | |
this.zoomSensitivity = zoomSensitivity; | |
} | |
/** | |
* Sets the default distance at start of applicaiton | |
* @param defaultDistance | |
*/ | |
public void setDefaultDistance(float defaultDistance) { | |
distance = defaultDistance; | |
targetDistance = distance; | |
} | |
/** | |
* sets the default horizontal rotation of the camera at start of the application | |
* @param angle | |
*/ | |
public void setDefaultHorizontalRotation(float angle) { | |
rotation = angle; | |
targetRotation = angle; | |
} | |
/** | |
* sets the current zoom distance for the chase camera | |
* @param new distance | |
*/ | |
public void alterDistance(float alterBy) { | |
this.zoomCamera(alterBy); | |
} | |
/** | |
* sets the default vertical rotation of the camera at start of the application | |
* @param angle | |
*/ | |
public void setDefaultVerticalRotation(float angle) { | |
vRotation = angle; | |
targetVRotation = angle; | |
} | |
public void collide() { | |
LinkedList<PhysicsRayTestResult> testResults; | |
testResults = (LinkedList) myPhysicsSpace.rayTest(target.getWorldTranslation(), maxPos); | |
float hitFraction = 1f; | |
if(testResults != null && testResults.size() > 0) { | |
hitFraction = testResults.getFirst().getHitFraction(); | |
} | |
targetDistance = ((float)((int)(hitFraction*100)))/100 * maxDistanceByZoom; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment