Started the reimplementation of the ChaseCamera as an AppState.

All basic features are there, smooth motion is still missing along with some very specific features that I'm tempted to just drop...
Also added a test case

git-svn-id: https://jmonkeyengine.googlecode.com/svn/branches/gradle-restructure@11102 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
experimental
rem..om 11 years ago
parent 240b41fdf6
commit bea196baa2
  1. 484
      jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java
  2. 153
      jme3-examples/src/main/java/jme3test/input/TestChaseCameraAppState.java

@ -0,0 +1,484 @@
/*
* Copyright (c) 2009-2012 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 com.jme3.app;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.CameraInput;
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.input.controls.Trigger;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.CameraControl;
import com.jme3.util.TempVars;
/**
* This class is a camera controler that allow the camera to follow a target
* Spatial.
*
* @author Nehon
*/
public class ChaseCameraAppState extends AbstractAppState implements ActionListener, AnalogListener {
protected Spatial spatial;
protected Node target;
protected CameraNode camNode;
protected InputManager inputManager;
protected boolean invertYaxis = false;
protected boolean invertXaxis = false;
protected boolean hideCursorOnRotate = true;
protected boolean canRotate;
protected boolean dragToRotate = true;
protected float rotationSpeed = 1.0f;
protected float zoomSpeed = 2.0f;
//protected boolean zoomin;
protected float minDistance = 1.0f;
protected float maxDistance = 40.0f;
protected float distance = 20;
protected float maxVerticalRotation = 1.4f;
protected float verticalRotation = 0f;
protected float minVerticalRotation = 0f;
protected float horizontalRotation = 0f;
//protected float distanceLerpFactor = 0;
protected Vector3f upVector;
protected Vector3f leftVector;
protected Trigger[] zoomOutTrigger = {new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)};
protected Trigger[] zoomInTrigger = {new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)};
protected Trigger[] toggleRotateTrigger = {new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)};
//
// protected boolean rotating = false;
// protected float rotation = 0;
// protected float targetRotation = rotation;
public ChaseCameraAppState() {
}
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.inputManager = app.getInputManager();
target = new Node("ChaseCamTarget");
camNode = new CameraNode("ChaseCameraNode", app.getCamera());
camNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera);
target.attachChild(camNode);
camNode.setLocalTranslation(0, 0, distance);
upVector = app.getCamera().getUp().clone();
leftVector = app.getCamera().getLeft().clone();
registerWithInput();
rotateCamera();
}
/**
* Registers inputs with the input manager
*
* @param inputManager
*/
public final void registerWithInput() {
String[] inputs = {CameraInput.CHASECAM_TOGGLEROTATE,
CameraInput.CHASECAM_DOWN,
CameraInput.CHASECAM_UP,
CameraInput.CHASECAM_MOVELEFT,
CameraInput.CHASECAM_MOVERIGHT,
CameraInput.CHASECAM_ZOOMIN,
CameraInput.CHASECAM_ZOOMOUT};
initVerticalAxisInputs();
initZoomInput();
initHorizontalAxisInput();
initTogleRotateInput();
inputManager.addListener(this, inputs);
}
public void onAction(String name, boolean keyPressed, float tpf) {
if (isEnabled()) {
if (dragToRotate) {
if (name.equals(CameraInput.CHASECAM_TOGGLEROTATE) && isEnabled()) {
if (keyPressed) {
canRotate = true;
if (hideCursorOnRotate) {
inputManager.setCursorVisible(false);
}
} else {
canRotate = false;
if (hideCursorOnRotate) {
inputManager.setCursorVisible(true);
}
}
}
}
}
}
public void onAnalog(String name, float value, float tpf) {
if (isEnabled()) {
if (canRotate) {
if (name.equals(CameraInput.CHASECAM_MOVELEFT)) {
horizontalRotation -= value * rotationSpeed;
rotateCamera();
} else if (name.equals(CameraInput.CHASECAM_MOVERIGHT)) {
horizontalRotation += value * rotationSpeed;
rotateCamera();
} else if (name.equals(CameraInput.CHASECAM_UP)) {
verticalRotation += value * rotationSpeed;
rotateCamera();
} else if (name.equals(CameraInput.CHASECAM_DOWN)) {
verticalRotation -= value * rotationSpeed;
rotateCamera();
}
}
if (name.equals(CameraInput.CHASECAM_ZOOMIN)) {
zoomCamera(-value * zoomSpeed);
} else if (name.equals(CameraInput.CHASECAM_ZOOMOUT)) {
zoomCamera(+value * zoomSpeed);
}
}
}
/**
* rotate the camera around the target
*/
protected void rotateCamera() {
verticalRotation = FastMath.clamp(verticalRotation, minVerticalRotation, maxVerticalRotation);
TempVars vars = TempVars.get();
Quaternion rot = vars.quat1;
Quaternion rot2 = vars.quat2;
rot.fromAngleNormalAxis(verticalRotation, leftVector);
rot2.fromAngleNormalAxis(horizontalRotation, upVector);
rot2.multLocal(rot);
target.setLocalRotation(rot2);
vars.release();
}
/**
* move the camera toward or away the target
*/
protected void zoomCamera(float value) {
distance = FastMath.clamp(distance + value, minDistance, maxDistance);
camNode.setLocalTranslation(new Vector3f(0, 0, distance));
}
public void setTarget(Spatial targetSpatial) {
spatial = targetSpatial;
}
@Override
public void update(float tpf) {
if (spatial == null) {
throw new IllegalArgumentException("The spatial to follow is null, please use the setTarget method");
}
target.setLocalTranslation(spatial.getWorldTranslation());
camNode.lookAt(target.getWorldTranslation(), upVector);
target.updateLogicalState(tpf);
target.updateGeometricState();
}
/**
* Sets custom triggers for toggleing the rotation of the cam deafult are
* new MouseButtonTrigger(MouseInput.BUTTON_LEFT) left mouse button new
* MouseButtonTrigger(MouseInput.BUTTON_RIGHT) right mouse button
*
* @param triggers
*/
public void setToggleRotationTrigger(Trigger... triggers) {
toggleRotateTrigger = triggers;
if (inputManager != null) {
inputManager.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE);
initTogleRotateInput();
inputManager.addListener(this, CameraInput.CHASECAM_TOGGLEROTATE);
}
}
/**
* Sets custom triggers for zomming in the cam default is new
* MouseAxisTrigger(MouseInput.AXIS_WHEEL, true) mouse wheel up
*
* @param triggers
*/
public void setZoomInTrigger(Trigger... triggers) {
zoomInTrigger = triggers;
if (inputManager != null) {
inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMIN);
inputManager.addMapping(CameraInput.CHASECAM_ZOOMIN, zoomInTrigger);
inputManager.addListener(this, CameraInput.CHASECAM_ZOOMIN);
}
}
/**
* Sets custom triggers for zomming out the cam default is new
* MouseAxisTrigger(MouseInput.AXIS_WHEEL, false) mouse wheel down
*
* @param triggers
*/
public void setZoomOutTrigger(Trigger... triggers) {
zoomOutTrigger = triggers;
if (inputManager != null) {
inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMOUT);
inputManager.addMapping(CameraInput.CHASECAM_ZOOMOUT, zoomOutTrigger);
inputManager.addListener(this, CameraInput.CHASECAM_ZOOMOUT);
}
}
/**
* 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;
zoomCamera(distance);
}
/**
* 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)
*/
public void setMinDistance(float minDistance) {
this.minDistance = minDistance;
zoomCamera(distance);
}
/**
* @return The maximal vertical rotation angle in radian of the camera
* around the target
*/
public float getMaxVerticalRotation() {
return maxVerticalRotation;
}
/**
* Sets the maximal vertical rotation angle in radian of the camera around
* the target. Default is Pi/2;
*
* @param maxVerticalRotation
*/
public void setMaxVerticalRotation(float maxVerticalRotation) {
this.maxVerticalRotation = maxVerticalRotation;
rotateCamera();
}
/**
*
* @return The minimal vertical rotation angle in radian of the camera
* around the target
*/
public float getMinVerticalRotation() {
return minVerticalRotation;
}
/**
* Sets the minimal vertical rotation angle in radian of the camera around
* the target default is 0;
*
* @param minHeight
*/
public void setMinVerticalRotation(float minHeight) {
this.minVerticalRotation = minHeight;
rotateCamera();
}
/**
* returns the zoom speed
*
* @return
*/
public float getZoomSpeed() {
return zoomSpeed;
}
/**
* Sets the zoom speed, the lower the value, the slower the camera will zoom
* in and out. default is 2.
*
* @param zoomSpeed
*/
public void setZoomSpeed(float zoomSpeed) {
this.zoomSpeed = zoomSpeed;
}
/**
* Returns the rotation speed when the mouse is moved.
*
* @return the rotation speed when the mouse is moved.
*/
public float getRotationSpeed() {
return rotationSpeed;
}
/**
* Sets the rotate amount when user moves his mouse, the lower the value,
* the slower the camera will rotate. default is 1.
*
* @param rotationSpeed Rotation speed on mouse movement, default is 1.
*/
public void setRotationSpeed(float rotationSpeed) {
this.rotationSpeed = rotationSpeed;
}
/**
* Sets the default distance at start of applicaiton
*
* @param defaultDistance
*/
public void setDefaultDistance(float defaultDistance) {
distance = defaultDistance;
}
/**
* sets the default horizontal rotation in radian of the camera at start of
* the application
*
* @param angleInRad
*/
public void setDefaultHorizontalRotation(float angleInRad) {
horizontalRotation = angleInRad;
}
/**
* sets the default vertical rotation in radian of the camera at start of
* the application
*
* @param angleInRad
*/
public void setDefaultVerticalRotation(float angleInRad) {
verticalRotation = angleInRad;
}
/**
* @return If drag to rotate feature is enabled.
*
* @see FlyByCamera#setDragToRotate(boolean)
*/
public boolean isDragToRotate() {
return dragToRotate;
}
/**
* @param dragToRotate When true, the user must hold the mouse button and
* drag over the screen to rotate the camera, and the cursor is visible
* until dragged. Otherwise, the cursor is invisible at all times and
* holding the mouse button is not needed to rotate the camera. This feature
* is disabled by default.
*/
public void setDragToRotate(boolean dragToRotate) {
this.dragToRotate = dragToRotate;
this.canRotate = !dragToRotate;
inputManager.setCursorVisible(dragToRotate);
}
/**
* invert the vertical axis movement of the mouse
*
* @param invertYaxis
*/
public void setInvertVerticalAxis(boolean invertYaxis) {
this.invertYaxis = invertYaxis;
if (inputManager != null) {
inputManager.deleteMapping(CameraInput.CHASECAM_DOWN);
inputManager.deleteMapping(CameraInput.CHASECAM_UP);
initVerticalAxisInputs();
inputManager.addListener(this, CameraInput.CHASECAM_DOWN, CameraInput.CHASECAM_UP);
}
}
/**
* invert the Horizontal axis movement of the mouse
*
* @param invertXaxis
*/
public void setInvertHorizontalAxis(boolean invertXaxis) {
this.invertXaxis = invertXaxis;
if (inputManager != null) {
inputManager.deleteMapping(CameraInput.CHASECAM_MOVELEFT);
inputManager.deleteMapping(CameraInput.CHASECAM_MOVERIGHT);
initHorizontalAxisInput();
inputManager.addListener(this, CameraInput.CHASECAM_MOVELEFT, CameraInput.CHASECAM_MOVERIGHT);
}
}
private void initVerticalAxisInputs() {
if (!invertYaxis) {
inputManager.addMapping(CameraInput.CHASECAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addMapping(CameraInput.CHASECAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
} else {
inputManager.addMapping(CameraInput.CHASECAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addMapping(CameraInput.CHASECAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
}
}
private void initHorizontalAxisInput() {
if (!invertXaxis) {
inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
} else {
inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
}
}
private void initZoomInput() {
inputManager.addMapping(CameraInput.CHASECAM_ZOOMIN, zoomInTrigger);
inputManager.addMapping(CameraInput.CHASECAM_ZOOMOUT, zoomOutTrigger);
}
private void initTogleRotateInput() {
inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, toggleRotateTrigger);
}
}

@ -0,0 +1,153 @@
/*
* Copyright (c) 2009-2012 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 jme3test.input;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.FlyCamAppState;
import com.jme3.app.SimpleApplication;
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.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
/** A 3rd-person chase camera orbits a target (teapot).
* Follow the teapot with WASD keys, rotate by dragging the mouse. */
public class TestChaseCameraAppState extends SimpleApplication implements AnalogListener, ActionListener {
private Geometry teaGeom;
public static void main(String[] args) {
TestChaseCameraAppState app = new TestChaseCameraAppState();
app.start();
}
public void simpleInitApp() {
// Load a teapot model
teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
Material mat_tea = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
teaGeom.setMaterial(mat_tea);
rootNode.attachChild(teaGeom);
// Load a floor model
Material mat_ground = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat_ground.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
Geometry ground = new Geometry("ground", new Quad(50, 50));
ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
ground.setLocalTranslation(-25, -1, 25);
ground.setMaterial(mat_ground);
rootNode.attachChild(ground);
//disable the flyCam
stateManager.detach(stateManager.getState(FlyCamAppState.class));
// Enable a chase cam
ChaseCameraAppState chaseCamAS = new ChaseCameraAppState();
chaseCamAS.setTarget(teaGeom);
stateManager.attach(chaseCamAS);
//Uncomment this to invert the camera's vertical rotation Axis
//chaseCamAS.setInvertVerticalAxis(true);
//Uncomment this to invert the camera's horizontal rotation Axis
//chaseCamAS.setInvertHorizontalAxis(true);
//Uncomment this to enable rotation when the middle mouse button is pressed (like Blender)
//WARNING : setting this trigger disable the rotation on right and left mouse button click
//chaseCamAS.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));
//Uncomment this to set mutiple triggers to enable rotation of the cam
//Here space bar and middle mouse button
//chaseCamAS.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE),new KeyTrigger(KeyInput.KEY_SPACE));
//registering inputs for target's movement
registerInput();
}
public void registerInput() {
inputManager.addMapping("moveForward", new KeyTrigger(KeyInput.KEY_UP), new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("moveBackward", new KeyTrigger(KeyInput.KEY_DOWN), new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("moveRight", new KeyTrigger(KeyInput.KEY_RIGHT), new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("moveLeft", new KeyTrigger(KeyInput.KEY_LEFT), new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("displayPosition", new KeyTrigger(KeyInput.KEY_P));
inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft");
inputManager.addListener(this, "displayPosition");
}
public void onAnalog(String name, float value, float tpf) {
if (name.equals("moveForward")) {
teaGeom.move(0, 0, -5 * tpf);
}
if (name.equals("moveBackward")) {
teaGeom.move(0, 0, 5 * tpf);
}
if (name.equals("moveRight")) {
teaGeom.move(5 * tpf, 0, 0);
}
if (name.equals("moveLeft")) {
teaGeom.move(-5 * tpf, 0, 0);
}
}
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("displayPosition") && keyPressed) {
teaGeom.move(10, 10, 10);
}
}
@Override
public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
// teaGeom.move(new Vector3f(0.001f, 0, 0));
// pivot.rotate(0, 0.00001f, 0);
// rootNode.updateGeometricState();
}
// public void update() {
// super.update();
//// render the viewports
// float tpf = timer.getTimePerFrame();
// state.getRootNode().rotate(0, 0.000001f, 0);
// stateManager.update(tpf);
// stateManager.render(renderManager);
// renderManager.render(tpf);
// }
}
Loading…
Cancel
Save