/* | |
* 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.input; | |
import com.jme3.collision.MotionAllowedListener; | |
import com.jme3.input.controls.*; | |
import com.jme3.math.FastMath; | |
import com.jme3.math.Matrix3f; | |
import com.jme3.math.Quaternion; | |
import com.jme3.math.Vector3f; | |
import com.jme3.renderer.Camera; | |
/** | |
* A first person view camera controller. | |
* After creation, you must register the camera controller with the | |
* dispatcher using #registerWithDispatcher(). | |
* | |
* Controls: | |
* - Move the mouse to rotate the camera | |
* - Mouse wheel for zooming in or out | |
* - WASD keys for moving forward/backward and strafing | |
* - QZ keys raise or lower the camera | |
*/ | |
public class FlyByCamera implements AnalogListener, ActionListener { | |
private static String[] mappings = new String[]{ | |
"FLYCAM_Left", | |
"FLYCAM_Right", | |
"FLYCAM_Up", | |
"FLYCAM_Down", | |
"FLYCAM_StrafeLeft", | |
"FLYCAM_StrafeRight", | |
"FLYCAM_Forward", | |
"FLYCAM_Backward", | |
"FLYCAM_ZoomIn", | |
"FLYCAM_ZoomOut", | |
"FLYCAM_RotateDrag", | |
"FLYCAM_Rise", | |
"FLYCAM_Lower" | |
}; | |
protected Camera cam; | |
protected Vector3f initialUpVec; | |
protected float rotationSpeed = 1f; | |
protected float moveSpeed = 3f; | |
protected MotionAllowedListener motionAllowed = null; | |
protected boolean enabled = true; | |
protected boolean dragToRotate = false; | |
protected boolean canRotate = false; | |
protected InputManager inputManager; | |
/** | |
* Creates a new FlyByCamera to control the given Camera object. | |
* @param cam | |
*/ | |
public FlyByCamera(Camera cam){ | |
this.cam = cam; | |
initialUpVec = cam.getUp().clone(); | |
} | |
/** | |
* Sets the up vector that should be used for the camera. | |
* @param upVec | |
*/ | |
public void setUpVector(Vector3f upVec) { | |
initialUpVec.set(upVec); | |
} | |
public void setMotionAllowedListener(MotionAllowedListener listener){ | |
this.motionAllowed = listener; | |
} | |
/** | |
* Sets the move speed. The speed is given in world units per second. | |
* @param moveSpeed | |
*/ | |
public void setMoveSpeed(float moveSpeed){ | |
this.moveSpeed = moveSpeed; | |
} | |
/** | |
* Sets the rotation speed. | |
* @param rotationSpeed | |
*/ | |
public void setRotationSpeed(float rotationSpeed){ | |
this.rotationSpeed = rotationSpeed; | |
} | |
/** | |
* @param enable If false, the camera will ignore input. | |
*/ | |
public void setEnabled(boolean enable){ | |
if (enabled && !enable){ | |
if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){ | |
inputManager.setCursorVisible(true); | |
} | |
} | |
enabled = enable; | |
} | |
/** | |
* @return If enabled | |
* @see FlyByCamera#setEnabled(boolean) | |
*/ | |
public boolean isEnabled(){ | |
return enabled; | |
} | |
/** | |
* @return If drag to rotate feature is enabled. | |
* | |
* @see FlyByCamera#setDragToRotate(boolean) | |
*/ | |
public boolean isDragToRotate() { | |
return dragToRotate; | |
} | |
/** | |
* Set if drag to rotate mode is enabled. | |
* | |
* 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. | |
* | |
* @param dragToRotate True if drag to rotate mode is enabled. | |
*/ | |
public void setDragToRotate(boolean dragToRotate) { | |
this.dragToRotate = dragToRotate; | |
if (inputManager != null) { | |
inputManager.setCursorVisible(dragToRotate); | |
} | |
} | |
/** | |
* Registers the FlyByCamera to receive input events from the provided | |
* Dispatcher. | |
* @param inputManager | |
*/ | |
public void registerWithInput(InputManager inputManager){ | |
this.inputManager = inputManager; | |
// both mouse and button - rotation of cam | |
inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true), | |
new KeyTrigger(KeyInput.KEY_LEFT)); | |
inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false), | |
new KeyTrigger(KeyInput.KEY_RIGHT)); | |
inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false), | |
new KeyTrigger(KeyInput.KEY_UP)); | |
inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true), | |
new KeyTrigger(KeyInput.KEY_DOWN)); | |
// mouse only - zoom in/out with wheel, and rotate drag | |
inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); | |
inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); | |
inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); | |
// keyboard only WASD for movement and WZ for rise/lower height | |
inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A)); | |
inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D)); | |
inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W)); | |
inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S)); | |
inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q)); | |
inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z)); | |
inputManager.addListener(this, mappings); | |
inputManager.setCursorVisible(dragToRotate || !isEnabled()); | |
Joystick[] joysticks = inputManager.getJoysticks(); | |
if (joysticks != null && joysticks.length > 0){ | |
Joystick joystick = joysticks[0]; | |
joystick.assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft", JoyInput.AXIS_POV_X); | |
joystick.assignAxis("FLYCAM_Forward", "FLYCAM_Backward", JoyInput.AXIS_POV_Y); | |
joystick.assignAxis("FLYCAM_Right", "FLYCAM_Left", joystick.getXAxisIndex()); | |
joystick.assignAxis("FLYCAM_Down", "FLYCAM_Up", joystick.getYAxisIndex()); | |
} | |
} | |
/** | |
* Registers the FlyByCamera to receive input events from the provided | |
* Dispatcher. | |
* @param inputManager | |
*/ | |
public void unregisterInput(){ | |
if (inputManager == null) { | |
return; | |
} | |
for (String s : mappings) { | |
if (inputManager.hasMapping(s)) { | |
inputManager.deleteMapping( s ); | |
} | |
} | |
inputManager.removeListener(this); | |
inputManager.setCursorVisible(!dragToRotate); | |
Joystick[] joysticks = inputManager.getJoysticks(); | |
if (joysticks != null && joysticks.length > 0){ | |
Joystick joystick = joysticks[0]; | |
// No way to unassing axis | |
} | |
} | |
protected void rotateCamera(float value, Vector3f axis){ | |
if (dragToRotate){ | |
if (canRotate){ | |
// value = -value; | |
}else{ | |
return; | |
} | |
} | |
Matrix3f mat = new Matrix3f(); | |
mat.fromAngleNormalAxis(rotationSpeed * value, axis); | |
Vector3f up = cam.getUp(); | |
Vector3f left = cam.getLeft(); | |
Vector3f dir = cam.getDirection(); | |
mat.mult(up, up); | |
mat.mult(left, left); | |
mat.mult(dir, dir); | |
Quaternion q = new Quaternion(); | |
q.fromAxes(left, up, dir); | |
q.normalize(); | |
cam.setAxes(q); | |
} | |
protected void zoomCamera(float value){ | |
// derive fovY value | |
float h = cam.getFrustumTop(); | |
float w = cam.getFrustumRight(); | |
float aspect = w / h; | |
float near = cam.getFrustumNear(); | |
float fovY = FastMath.atan(h / near) | |
/ (FastMath.DEG_TO_RAD * .5f); | |
fovY += value * 0.1f; | |
h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near; | |
w = h * aspect; | |
cam.setFrustumTop(h); | |
cam.setFrustumBottom(-h); | |
cam.setFrustumLeft(-w); | |
cam.setFrustumRight(w); | |
} | |
protected void riseCamera(float value){ | |
Vector3f vel = new Vector3f(0, value * moveSpeed, 0); | |
Vector3f pos = cam.getLocation().clone(); | |
if (motionAllowed != null) | |
motionAllowed.checkMotionAllowed(pos, vel); | |
else | |
pos.addLocal(vel); | |
cam.setLocation(pos); | |
} | |
protected void moveCamera(float value, boolean sideways){ | |
Vector3f vel = new Vector3f(); | |
Vector3f pos = cam.getLocation().clone(); | |
if (sideways){ | |
cam.getLeft(vel); | |
}else{ | |
cam.getDirection(vel); | |
} | |
vel.multLocal(value * moveSpeed); | |
if (motionAllowed != null) | |
motionAllowed.checkMotionAllowed(pos, vel); | |
else | |
pos.addLocal(vel); | |
cam.setLocation(pos); | |
} | |
public void onAnalog(String name, float value, float tpf) { | |
if (!enabled) | |
return; | |
if (name.equals("FLYCAM_Left")){ | |
rotateCamera(value, initialUpVec); | |
}else if (name.equals("FLYCAM_Right")){ | |
rotateCamera(-value, initialUpVec); | |
}else if (name.equals("FLYCAM_Up")){ | |
rotateCamera(-value, cam.getLeft()); | |
}else if (name.equals("FLYCAM_Down")){ | |
rotateCamera(value, cam.getLeft()); | |
}else if (name.equals("FLYCAM_Forward")){ | |
moveCamera(value, false); | |
}else if (name.equals("FLYCAM_Backward")){ | |
moveCamera(-value, false); | |
}else if (name.equals("FLYCAM_StrafeLeft")){ | |
moveCamera(value, true); | |
}else if (name.equals("FLYCAM_StrafeRight")){ | |
moveCamera(-value, true); | |
}else if (name.equals("FLYCAM_Rise")){ | |
riseCamera(value); | |
}else if (name.equals("FLYCAM_Lower")){ | |
riseCamera(-value); | |
}else if (name.equals("FLYCAM_ZoomIn")){ | |
zoomCamera(value); | |
}else if (name.equals("FLYCAM_ZoomOut")){ | |
zoomCamera(-value); | |
} | |
} | |
public void onAction(String name, boolean value, float tpf) { | |
if (!enabled) | |
return; | |
if (name.equals("FLYCAM_RotateDrag") && dragToRotate){ | |
canRotate = value; | |
inputManager.setCursorVisible(!value); | |
} | |
} | |
} |