Home | History | Annotate | Download | only in input
      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