1 /* 2 * Copyright (c) 2009-2012 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.input; 34 35 import com.jme3.collision.MotionAllowedListener; 36 import com.jme3.input.controls.*; 37 import com.jme3.math.FastMath; 38 import com.jme3.math.Matrix3f; 39 import com.jme3.math.Quaternion; 40 import com.jme3.math.Vector3f; 41 import com.jme3.renderer.Camera; 42 43 /** 44 * A first person view camera controller. 45 * After creation, you must register the camera controller with the 46 * dispatcher using #registerWithDispatcher(). 47 * 48 * Controls: 49 * - Move the mouse to rotate the camera 50 * - Mouse wheel for zooming in or out 51 * - WASD keys for moving forward/backward and strafing 52 * - QZ keys raise or lower the camera 53 */ 54 public class FlyByCamera implements AnalogListener, ActionListener { 55 56 private static String[] mappings = new String[]{ 57 "FLYCAM_Left", 58 "FLYCAM_Right", 59 "FLYCAM_Up", 60 "FLYCAM_Down", 61 62 "FLYCAM_StrafeLeft", 63 "FLYCAM_StrafeRight", 64 "FLYCAM_Forward", 65 "FLYCAM_Backward", 66 67 "FLYCAM_ZoomIn", 68 "FLYCAM_ZoomOut", 69 "FLYCAM_RotateDrag", 70 71 "FLYCAM_Rise", 72 "FLYCAM_Lower" 73 }; 74 75 protected Camera cam; 76 protected Vector3f initialUpVec; 77 protected float rotationSpeed = 1f; 78 protected float moveSpeed = 3f; 79 protected MotionAllowedListener motionAllowed = null; 80 protected boolean enabled = true; 81 protected boolean dragToRotate = false; 82 protected boolean canRotate = false; 83 protected InputManager inputManager; 84 85 /** 86 * Creates a new FlyByCamera to control the given Camera object. 87 * @param cam 88 */ 89 public FlyByCamera(Camera cam){ 90 this.cam = cam; 91 initialUpVec = cam.getUp().clone(); 92 } 93 94 /** 95 * Sets the up vector that should be used for the camera. 96 * @param upVec 97 */ 98 public void setUpVector(Vector3f upVec) { 99 initialUpVec.set(upVec); 100 } 101 102 public void setMotionAllowedListener(MotionAllowedListener listener){ 103 this.motionAllowed = listener; 104 } 105 106 /** 107 * Sets the move speed. The speed is given in world units per second. 108 * @param moveSpeed 109 */ 110 public void setMoveSpeed(float moveSpeed){ 111 this.moveSpeed = moveSpeed; 112 } 113 114 /** 115 * Sets the rotation speed. 116 * @param rotationSpeed 117 */ 118 public void setRotationSpeed(float rotationSpeed){ 119 this.rotationSpeed = rotationSpeed; 120 } 121 122 /** 123 * @param enable If false, the camera will ignore input. 124 */ 125 public void setEnabled(boolean enable){ 126 if (enabled && !enable){ 127 if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){ 128 inputManager.setCursorVisible(true); 129 } 130 } 131 enabled = enable; 132 } 133 134 /** 135 * @return If enabled 136 * @see FlyByCamera#setEnabled(boolean) 137 */ 138 public boolean isEnabled(){ 139 return enabled; 140 } 141 142 /** 143 * @return If drag to rotate feature is enabled. 144 * 145 * @see FlyByCamera#setDragToRotate(boolean) 146 */ 147 public boolean isDragToRotate() { 148 return dragToRotate; 149 } 150 151 /** 152 * Set if drag to rotate mode is enabled. 153 * 154 * When true, the user must hold the mouse button 155 * and drag over the screen to rotate the camera, and the cursor is 156 * visible until dragged. Otherwise, the cursor is invisible at all times 157 * and holding the mouse button is not needed to rotate the camera. 158 * This feature is disabled by default. 159 * 160 * @param dragToRotate True if drag to rotate mode is enabled. 161 */ 162 public void setDragToRotate(boolean dragToRotate) { 163 this.dragToRotate = dragToRotate; 164 if (inputManager != null) { 165 inputManager.setCursorVisible(dragToRotate); 166 } 167 } 168 169 /** 170 * Registers the FlyByCamera to receive input events from the provided 171 * Dispatcher. 172 * @param inputManager 173 */ 174 public void registerWithInput(InputManager inputManager){ 175 this.inputManager = inputManager; 176 177 // both mouse and button - rotation of cam 178 inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true), 179 new KeyTrigger(KeyInput.KEY_LEFT)); 180 181 inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false), 182 new KeyTrigger(KeyInput.KEY_RIGHT)); 183 184 inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false), 185 new KeyTrigger(KeyInput.KEY_UP)); 186 187 inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true), 188 new KeyTrigger(KeyInput.KEY_DOWN)); 189 190 // mouse only - zoom in/out with wheel, and rotate drag 191 inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); 192 inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); 193 inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); 194 195 // keyboard only WASD for movement and WZ for rise/lower height 196 inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A)); 197 inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D)); 198 inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W)); 199 inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S)); 200 inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q)); 201 inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z)); 202 203 inputManager.addListener(this, mappings); 204 inputManager.setCursorVisible(dragToRotate || !isEnabled()); 205 206 Joystick[] joysticks = inputManager.getJoysticks(); 207 if (joysticks != null && joysticks.length > 0){ 208 Joystick joystick = joysticks[0]; 209 joystick.assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft", JoyInput.AXIS_POV_X); 210 joystick.assignAxis("FLYCAM_Forward", "FLYCAM_Backward", JoyInput.AXIS_POV_Y); 211 joystick.assignAxis("FLYCAM_Right", "FLYCAM_Left", joystick.getXAxisIndex()); 212 joystick.assignAxis("FLYCAM_Down", "FLYCAM_Up", joystick.getYAxisIndex()); 213 } 214 } 215 216 /** 217 * Registers the FlyByCamera to receive input events from the provided 218 * Dispatcher. 219 * @param inputManager 220 */ 221 public void unregisterInput(){ 222 223 if (inputManager == null) { 224 return; 225 } 226 227 for (String s : mappings) { 228 if (inputManager.hasMapping(s)) { 229 inputManager.deleteMapping( s ); 230 } 231 } 232 233 inputManager.removeListener(this); 234 inputManager.setCursorVisible(!dragToRotate); 235 236 Joystick[] joysticks = inputManager.getJoysticks(); 237 if (joysticks != null && joysticks.length > 0){ 238 Joystick joystick = joysticks[0]; 239 240 // No way to unassing axis 241 } 242 } 243 244 protected void rotateCamera(float value, Vector3f axis){ 245 if (dragToRotate){ 246 if (canRotate){ 247 // value = -value; 248 }else{ 249 return; 250 } 251 } 252 253 Matrix3f mat = new Matrix3f(); 254 mat.fromAngleNormalAxis(rotationSpeed * value, axis); 255 256 Vector3f up = cam.getUp(); 257 Vector3f left = cam.getLeft(); 258 Vector3f dir = cam.getDirection(); 259 260 mat.mult(up, up); 261 mat.mult(left, left); 262 mat.mult(dir, dir); 263 264 Quaternion q = new Quaternion(); 265 q.fromAxes(left, up, dir); 266 q.normalize(); 267 268 cam.setAxes(q); 269 } 270 271 protected void zoomCamera(float value){ 272 // derive fovY value 273 float h = cam.getFrustumTop(); 274 float w = cam.getFrustumRight(); 275 float aspect = w / h; 276 277 float near = cam.getFrustumNear(); 278 279 float fovY = FastMath.atan(h / near) 280 / (FastMath.DEG_TO_RAD * .5f); 281 fovY += value * 0.1f; 282 283 h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near; 284 w = h * aspect; 285 286 cam.setFrustumTop(h); 287 cam.setFrustumBottom(-h); 288 cam.setFrustumLeft(-w); 289 cam.setFrustumRight(w); 290 } 291 292 protected void riseCamera(float value){ 293 Vector3f vel = new Vector3f(0, value * moveSpeed, 0); 294 Vector3f pos = cam.getLocation().clone(); 295 296 if (motionAllowed != null) 297 motionAllowed.checkMotionAllowed(pos, vel); 298 else 299 pos.addLocal(vel); 300 301 cam.setLocation(pos); 302 } 303 304 protected void moveCamera(float value, boolean sideways){ 305 Vector3f vel = new Vector3f(); 306 Vector3f pos = cam.getLocation().clone(); 307 308 if (sideways){ 309 cam.getLeft(vel); 310 }else{ 311 cam.getDirection(vel); 312 } 313 vel.multLocal(value * moveSpeed); 314 315 if (motionAllowed != null) 316 motionAllowed.checkMotionAllowed(pos, vel); 317 else 318 pos.addLocal(vel); 319 320 cam.setLocation(pos); 321 } 322 323 public void onAnalog(String name, float value, float tpf) { 324 if (!enabled) 325 return; 326 327 if (name.equals("FLYCAM_Left")){ 328 rotateCamera(value, initialUpVec); 329 }else if (name.equals("FLYCAM_Right")){ 330 rotateCamera(-value, initialUpVec); 331 }else if (name.equals("FLYCAM_Up")){ 332 rotateCamera(-value, cam.getLeft()); 333 }else if (name.equals("FLYCAM_Down")){ 334 rotateCamera(value, cam.getLeft()); 335 }else if (name.equals("FLYCAM_Forward")){ 336 moveCamera(value, false); 337 }else if (name.equals("FLYCAM_Backward")){ 338 moveCamera(-value, false); 339 }else if (name.equals("FLYCAM_StrafeLeft")){ 340 moveCamera(value, true); 341 }else if (name.equals("FLYCAM_StrafeRight")){ 342 moveCamera(-value, true); 343 }else if (name.equals("FLYCAM_Rise")){ 344 riseCamera(value); 345 }else if (name.equals("FLYCAM_Lower")){ 346 riseCamera(-value); 347 }else if (name.equals("FLYCAM_ZoomIn")){ 348 zoomCamera(value); 349 }else if (name.equals("FLYCAM_ZoomOut")){ 350 zoomCamera(-value); 351 } 352 } 353 354 public void onAction(String name, boolean value, float tpf) { 355 if (!enabled) 356 return; 357 358 if (name.equals("FLYCAM_RotateDrag") && dragToRotate){ 359 canRotate = value; 360 inputManager.setCursorVisible(!value); 361 } 362 } 363 364 } 365