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 package com.jme3.input;
     33 
     34 import com.jme3.app.Application;
     35 import com.jme3.input.controls.*;
     36 import com.jme3.input.event.*;
     37 import com.jme3.math.FastMath;
     38 import com.jme3.math.Vector2f;
     39 import com.jme3.util.IntMap;
     40 import com.jme3.util.IntMap.Entry;
     41 import java.util.ArrayList;
     42 import java.util.HashMap;
     43 import java.util.logging.Level;
     44 import java.util.logging.Logger;
     45 
     46 /**
     47  * The <code>InputManager</code> is responsible for converting input events
     48  * received from the Key, Mouse and Joy Input implementations into an
     49  * abstract, input device independent representation that user code can use.
     50  * <p>
     51  * By default an <code>InputManager</code> is included with every Application instance for use
     52  * in user code to query input, unless the Application is created as headless
     53  * or with input explicitly disabled.
     54  * <p>
     55  * The input manager has two concepts, a {@link Trigger} and a mapping.
     56  * A trigger represents a specific input trigger, such as a key button,
     57  * or a mouse axis. A mapping represents a link onto one or several triggers,
     58  * when the appropriate trigger is activated (e.g. a key is pressed), the
     59  * mapping will be invoked. Any listeners registered to receive an event
     60  * from the mapping will have an event raised.
     61  * <p>
     62  * There are two types of events that {@link InputListener input listeners}
     63  * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action}
     64  * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog}
     65  * events.
     66  * <p>
     67  * <code>onAction</code> events are raised when the specific input
     68  * activates or deactivates. For a digital input such as key press, the <code>onAction()</code>
     69  * event will be raised with the <code>isPressed</code> argument equal to true,
     70  * when the key is released, <code>onAction</code> is called again but this time
     71  * with the <code>isPressed</code> argument set to false.
     72  * For analog inputs, the <code>onAction</code> method will be called any time
     73  * the input is non-zero, however an exception to this is for joystick axis inputs,
     74  * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}.
     75  * <p>
     76  * <code>onAnalog</code> events are raised every frame while the input is activated.
     77  * For digital inputs, every frame that the input is active will cause the
     78  * <code>onAnalog</code> method to be called, the argument <code>value</code>
     79  * argument will equal to the frame's time per frame (TPF) value but only
     80  * for digital inputs. For analog inputs however, the <code>value</code> argument
     81  * will equal the actual analog value.
     82  */
     83 public class InputManager implements RawInputListener {
     84 
     85     private static final Logger logger = Logger.getLogger(InputManager.class.getName());
     86     private final KeyInput keys;
     87     private final MouseInput mouse;
     88     private final JoyInput joystick;
     89     private final TouchInput touch;
     90     private float frameTPF;
     91     private long lastLastUpdateTime = 0;
     92     private long lastUpdateTime = 0;
     93     private long frameDelta = 0;
     94     private long firstTime = 0;
     95     private boolean eventsPermitted = false;
     96     private boolean mouseVisible = true;
     97     private boolean safeMode = false;
     98     private float axisDeadZone = 0.05f;
     99     private Vector2f cursorPos = new Vector2f();
    100     private Joystick[] joysticks;
    101     private final IntMap<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>();
    102     private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>();
    103     private final IntMap<Long> pressedButtons = new IntMap<Long>();
    104     private final IntMap<Float> axisValues = new IntMap<Float>();
    105     private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>();
    106     private RawInputListener[] rawListenerArray = null;
    107     private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();
    108 
    109     private static class Mapping {
    110 
    111         private final String name;
    112         private final ArrayList<Integer> triggers = new ArrayList<Integer>();
    113         private final ArrayList<InputListener> listeners = new ArrayList<InputListener>();
    114 
    115         public Mapping(String name) {
    116             this.name = name;
    117         }
    118     }
    119 
    120     /**
    121      * Initializes the InputManager.
    122      *
    123      * <p>This should only be called internally in {@link Application}.
    124      *
    125      * @param mouse
    126      * @param keys
    127      * @param joystick
    128      * @param touch
    129      * @throws IllegalArgumentException If either mouseInput or keyInput are null.
    130      */
    131     public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) {
    132         if (keys == null || mouse == null) {
    133             throw new NullPointerException("Mouse or keyboard cannot be null");
    134         }
    135 
    136         this.keys = keys;
    137         this.mouse = mouse;
    138         this.joystick = joystick;
    139         this.touch = touch;
    140 
    141         keys.setInputListener(this);
    142         mouse.setInputListener(this);
    143         if (joystick != null) {
    144             joystick.setInputListener(this);
    145             joysticks = joystick.loadJoysticks(this);
    146         }
    147         if (touch != null) {
    148             touch.setInputListener(this);
    149         }
    150 
    151         firstTime = keys.getInputTimeNanos();
    152     }
    153 
    154     private void invokeActions(int hash, boolean pressed) {
    155         ArrayList<Mapping> maps = bindings.get(hash);
    156         if (maps == null) {
    157             return;
    158         }
    159 
    160         int size = maps.size();
    161         for (int i = size - 1; i >= 0; i--) {
    162             Mapping mapping = maps.get(i);
    163             ArrayList<InputListener> listeners = mapping.listeners;
    164             int listenerSize = listeners.size();
    165             for (int j = listenerSize - 1; j >= 0; j--) {
    166                 InputListener listener = listeners.get(j);
    167                 if (listener instanceof ActionListener) {
    168                     ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF);
    169                 }
    170             }
    171         }
    172     }
    173 
    174     private float computeAnalogValue(long timeDelta) {
    175         if (safeMode || frameDelta == 0) {
    176             return 1f;
    177         } else {
    178             return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1);
    179         }
    180     }
    181 
    182     private void invokeTimedActions(int hash, long time, boolean pressed) {
    183         if (!bindings.containsKey(hash)) {
    184             return;
    185         }
    186 
    187         if (pressed) {
    188             pressedButtons.put(hash, time);
    189         } else {
    190             Long pressTimeObj = pressedButtons.remove(hash);
    191             if (pressTimeObj == null) {
    192                 return; // under certain circumstances it can be null, ignore
    193             }                        // the event then.
    194 
    195             long pressTime = pressTimeObj;
    196             long lastUpdate = lastLastUpdateTime;
    197             long releaseTime = time;
    198             long timeDelta = releaseTime - Math.max(pressTime, lastUpdate);
    199 
    200             if (timeDelta > 0) {
    201                 invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
    202             }
    203         }
    204     }
    205 
    206     private void invokeUpdateActions() {
    207       if (pressedButtons.size() > 0) for (Entry<Long> pressedButton : pressedButtons) {
    208             int hash = pressedButton.getKey();
    209 
    210             long pressTime = pressedButton.getValue();
    211             long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime);
    212 
    213             if (timeDelta > 0) {
    214                 invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
    215             }
    216         }
    217 
    218       if (axisValues.size() > 0) for (Entry<Float> axisValue : axisValues) {
    219             int hash = axisValue.getKey();
    220             float value = axisValue.getValue();
    221             invokeAnalogs(hash, value * frameTPF, true);
    222         }
    223     }
    224 
    225     private void invokeAnalogs(int hash, float value, boolean isAxis) {
    226         ArrayList<Mapping> maps = bindings.get(hash);
    227         if (maps == null) {
    228             return;
    229         }
    230 
    231         if (!isAxis) {
    232             value *= frameTPF;
    233         }
    234 
    235         int size = maps.size();
    236         for (int i = size - 1; i >= 0; i--) {
    237             Mapping mapping = maps.get(i);
    238             ArrayList<InputListener> listeners = mapping.listeners;
    239             int listenerSize = listeners.size();
    240             for (int j = listenerSize - 1; j >= 0; j--) {
    241                 InputListener listener = listeners.get(j);
    242                 if (listener instanceof AnalogListener) {
    243                     // NOTE: multiply by TPF for any button bindings
    244                     ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
    245                 }
    246             }
    247         }
    248     }
    249 
    250     private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) {
    251         if (value < axisDeadZone) {
    252             invokeAnalogs(hash, value, !applyTpf);
    253             return;
    254         }
    255 
    256         ArrayList<Mapping> maps = bindings.get(hash);
    257         if (maps == null) {
    258             return;
    259         }
    260 
    261         boolean valueChanged = !axisValues.containsKey(hash);
    262         if (applyTpf) {
    263             value *= frameTPF;
    264         }
    265 
    266         int size = maps.size();
    267         for (int i = size - 1; i >= 0; i--) {
    268             Mapping mapping = maps.get(i);
    269             ArrayList<InputListener> listeners = mapping.listeners;
    270             int listenerSize = listeners.size();
    271             for (int j = listenerSize - 1; j >= 0; j--) {
    272                 InputListener listener = listeners.get(j);
    273 
    274                 if (listener instanceof ActionListener && valueChanged) {
    275                     ((ActionListener) listener).onAction(mapping.name, true, frameTPF);
    276                 }
    277 
    278                 if (listener instanceof AnalogListener) {
    279                     ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
    280                 }
    281 
    282             }
    283         }
    284     }
    285 
    286     /**
    287      * Callback from RawInputListener. Do not use.
    288      */
    289     public void beginInput() {
    290     }
    291 
    292     /**
    293      * Callback from RawInputListener. Do not use.
    294      */
    295     public void endInput() {
    296     }
    297 
    298     private void onJoyAxisEventQueued(JoyAxisEvent evt) {
    299 //        for (int i = 0; i < rawListeners.size(); i++){
    300 //            rawListeners.get(i).onJoyAxisEvent(evt);
    301 //        }
    302 
    303         int joyId = evt.getJoyIndex();
    304         int axis = evt.getAxisIndex();
    305         float value = evt.getValue();
    306         if (value < axisDeadZone && value > -axisDeadZone) {
    307             int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
    308             int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
    309 
    310             Float val1 = axisValues.get(hash1);
    311             Float val2 = axisValues.get(hash2);
    312 
    313             if (val1 != null && val1.floatValue() > axisDeadZone) {
    314                 invokeActions(hash1, false);
    315             }
    316             if (val2 != null && val2.floatValue() > axisDeadZone) {
    317                 invokeActions(hash2, false);
    318             }
    319 
    320             axisValues.remove(hash1);
    321             axisValues.remove(hash2);
    322 
    323         } else if (value < 0) {
    324             int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
    325             int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
    326             invokeAnalogsAndActions(hash, -value, true);
    327             axisValues.put(hash, -value);
    328             axisValues.remove(otherHash);
    329         } else {
    330             int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
    331             int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
    332             invokeAnalogsAndActions(hash, value, true);
    333             axisValues.put(hash, value);
    334             axisValues.remove(otherHash);
    335         }
    336     }
    337 
    338     /**
    339      * Callback from RawInputListener. Do not use.
    340      */
    341     public void onJoyAxisEvent(JoyAxisEvent evt) {
    342         if (!eventsPermitted) {
    343             throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
    344         }
    345 
    346         inputQueue.add(evt);
    347     }
    348 
    349     private void onJoyButtonEventQueued(JoyButtonEvent evt) {
    350 //        for (int i = 0; i < rawListeners.size(); i++){
    351 //            rawListeners.get(i).onJoyButtonEvent(evt);
    352 //        }
    353 
    354         int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex());
    355         invokeActions(hash, evt.isPressed());
    356         invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    357     }
    358 
    359     /**
    360      * Callback from RawInputListener. Do not use.
    361      */
    362     public void onJoyButtonEvent(JoyButtonEvent evt) {
    363         if (!eventsPermitted) {
    364             throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
    365         }
    366 
    367         inputQueue.add(evt);
    368     }
    369 
    370     private void onMouseMotionEventQueued(MouseMotionEvent evt) {
    371 //        for (int i = 0; i < rawListeners.size(); i++){
    372 //            rawListeners.get(i).onMouseMotionEvent(evt);
    373 //        }
    374 
    375         if (evt.getDX() != 0) {
    376             float val = Math.abs(evt.getDX()) / 1024f;
    377             invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false);
    378         }
    379         if (evt.getDY() != 0) {
    380             float val = Math.abs(evt.getDY()) / 1024f;
    381             invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false);
    382         }
    383         if (evt.getDeltaWheel() != 0) {
    384             float val = Math.abs(evt.getDeltaWheel()) / 100f;
    385             invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false);
    386         }
    387     }
    388 
    389     /**
    390      * Callback from RawInputListener. Do not use.
    391      */
    392     public void onMouseMotionEvent(MouseMotionEvent evt) {
    393         if (!eventsPermitted) {
    394             throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
    395         }
    396 
    397         cursorPos.set(evt.getX(), evt.getY());
    398         inputQueue.add(evt);
    399     }
    400 
    401     private void onMouseButtonEventQueued(MouseButtonEvent evt) {
    402         int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex());
    403         invokeActions(hash, evt.isPressed());
    404         invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    405     }
    406 
    407     /**
    408      * Callback from RawInputListener. Do not use.
    409      */
    410     public void onMouseButtonEvent(MouseButtonEvent evt) {
    411         if (!eventsPermitted) {
    412             throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
    413         }
    414         //updating cursor pos on click, so that non android touch events can properly update cursor position.
    415         cursorPos.set(evt.getX(), evt.getY());
    416         inputQueue.add(evt);
    417     }
    418 
    419     private void onKeyEventQueued(KeyInputEvent evt) {
    420         if (evt.isRepeating()) {
    421             return; // repeat events not used for bindings
    422         }
    423 
    424         int hash = KeyTrigger.keyHash(evt.getKeyCode());
    425         invokeActions(hash, evt.isPressed());
    426         invokeTimedActions(hash, evt.getTime(), evt.isPressed());
    427     }
    428 
    429     /**
    430      * Callback from RawInputListener. Do not use.
    431      */
    432     public void onKeyEvent(KeyInputEvent evt) {
    433         if (!eventsPermitted) {
    434             throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time.");
    435         }
    436 
    437         inputQueue.add(evt);
    438     }
    439 
    440     /**
    441      * Set the deadzone for joystick axes.
    442      *
    443      * <p>{@link ActionListener#onAction(java.lang.String, boolean, float) }
    444      * events will only be raised if the joystick axis value is greater than
    445      * the <code>deadZone</code>.
    446      *
    447      * @param deadZone the deadzone for joystick axes.
    448      */
    449     public void setAxisDeadZone(float deadZone) {
    450         this.axisDeadZone = deadZone;
    451     }
    452 
    453     /**
    454      * Returns the deadzone for joystick axes.
    455      *
    456      * @return the deadzone for joystick axes.
    457      */
    458     public float getAxisDeadZone() {
    459         return axisDeadZone;
    460     }
    461 
    462     /**
    463      * Adds a new listener to receive events on the given mappings.
    464      *
    465      * <p>The given InputListener will be registered to receive events
    466      * on the specified mapping names. When a mapping raises an event, the
    467      * listener will have its appropriate method invoked, either
    468      * {@link ActionListener#onAction(java.lang.String, boolean, float) }
    469      * or {@link AnalogListener#onAnalog(java.lang.String, float, float) }
    470      * depending on which interface the <code>listener</code> implements.
    471      * If the listener implements both interfaces, then it will receive the
    472      * appropriate event for each method.
    473      *
    474      * @param listener The listener to register to receive input events.
    475      * @param mappingNames The mapping names which the listener will receive
    476      * events from.
    477      *
    478      * @see InputManager#removeListener(com.jme3.input.controls.InputListener)
    479      */
    480     public void addListener(InputListener listener, String... mappingNames) {
    481         for (String mappingName : mappingNames) {
    482             Mapping mapping = mappings.get(mappingName);
    483             if (mapping == null) {
    484                 mapping = new Mapping(mappingName);
    485                 mappings.put(mappingName, mapping);
    486             }
    487             if (!mapping.listeners.contains(listener)) {
    488                 mapping.listeners.add(listener);
    489             }
    490         }
    491     }
    492 
    493     /**
    494      * Removes a listener from receiving events.
    495      *
    496      * <p>This will unregister the listener from any mappings that it
    497      * was previously registered with via
    498      * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }.
    499      *
    500      * @param listener The listener to unregister.
    501      *
    502      * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[])
    503      */
    504     public void removeListener(InputListener listener) {
    505         for (Mapping mapping : mappings.values()) {
    506             mapping.listeners.remove(listener);
    507         }
    508     }
    509 
    510     /**
    511      * Create a new mapping to the given triggers.
    512      *
    513      * <p>
    514      * The given mapping will be assigned to the given triggers, when
    515      * any of the triggers given raise an event, the listeners
    516      * registered to the mappings will receive appropriate events.
    517      *
    518      * @param mappingName The mapping name to assign.
    519      * @param triggers The triggers to which the mapping is to be registered.
    520      *
    521      * @see InputManager#deleteMapping(java.lang.String)
    522      */
    523     public void addMapping(String mappingName, Trigger... triggers) {
    524         Mapping mapping = mappings.get(mappingName);
    525         if (mapping == null) {
    526             mapping = new Mapping(mappingName);
    527             mappings.put(mappingName, mapping);
    528         }
    529 
    530         for (Trigger trigger : triggers) {
    531             int hash = trigger.triggerHashCode();
    532             ArrayList<Mapping> names = bindings.get(hash);
    533             if (names == null) {
    534                 names = new ArrayList<Mapping>();
    535                 bindings.put(hash, names);
    536             }
    537             if (!names.contains(mapping)) {
    538                 names.add(mapping);
    539                 mapping.triggers.add(hash);
    540             } else {
    541                 logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName);
    542             }
    543         }
    544     }
    545 
    546     /**
    547      * Returns true if this InputManager has a mapping registered
    548      * for the given mappingName.
    549      *
    550      * @param mappingName The mapping name to check.
    551      *
    552      * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
    553      * @see InputManager#deleteMapping(java.lang.String)
    554      */
    555     public boolean hasMapping(String mappingName) {
    556         return mappings.containsKey(mappingName);
    557     }
    558 
    559     /**
    560      * Deletes a mapping from receiving trigger events.
    561      *
    562      * <p>
    563      * The given mapping will no longer be assigned to receive trigger
    564      * events.
    565      *
    566      * @param mappingName The mapping name to unregister.
    567      *
    568      * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
    569      */
    570     public void deleteMapping(String mappingName) {
    571         Mapping mapping = mappings.remove(mappingName);
    572         if (mapping == null) {
    573             throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
    574         }
    575 
    576         ArrayList<Integer> triggers = mapping.triggers;
    577         for (int i = triggers.size() - 1; i >= 0; i--) {
    578             int hash = triggers.get(i);
    579             ArrayList<Mapping> maps = bindings.get(hash);
    580             maps.remove(mapping);
    581         }
    582     }
    583 
    584     /**
    585      * Deletes a specific trigger registered to a mapping.
    586      *
    587      * <p>
    588      * The given mapping will no longer receive events raised by the
    589      * trigger.
    590      *
    591      * @param mappingName The mapping name to cease receiving events from the
    592      * trigger.
    593      * @param trigger The trigger to no longer invoke events on the mapping.
    594      */
    595     public void deleteTrigger(String mappingName, Trigger trigger) {
    596         Mapping mapping = mappings.get(mappingName);
    597         if (mapping == null) {
    598             throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
    599         }
    600 
    601         ArrayList<Mapping> maps = bindings.get(trigger.triggerHashCode());
    602         maps.remove(mapping);
    603 
    604     }
    605 
    606     /**
    607      * Clears all the input mappings from this InputManager.
    608      * Consequently, also clears all of the
    609      * InputListeners as well.
    610      */
    611     public void clearMappings() {
    612         mappings.clear();
    613         bindings.clear();
    614         reset();
    615     }
    616 
    617     /**
    618      * Do not use.
    619      * Called to reset pressed keys or buttons when focus is restored.
    620      */
    621     public void reset() {
    622         pressedButtons.clear();
    623         axisValues.clear();
    624     }
    625 
    626     /**
    627      * Returns whether the mouse cursor is visible or not.
    628      *
    629      * <p>By default the cursor is visible.
    630      *
    631      * @return whether the mouse cursor is visible or not.
    632      *
    633      * @see InputManager#setCursorVisible(boolean)
    634      */
    635     public boolean isCursorVisible() {
    636         return mouseVisible;
    637     }
    638 
    639     /**
    640      * Set whether the mouse cursor should be visible or not.
    641      *
    642      * @param visible whether the mouse cursor should be visible or not.
    643      */
    644     public void setCursorVisible(boolean visible) {
    645         if (mouseVisible != visible) {
    646             mouseVisible = visible;
    647             mouse.setCursorVisible(mouseVisible);
    648         }
    649     }
    650 
    651     /**
    652      * Returns the current cursor position. The position is relative to the
    653      * bottom-left of the screen and is in pixels.
    654      *
    655      * @return the current cursor position
    656      */
    657     public Vector2f getCursorPosition() {
    658         return cursorPos;
    659     }
    660 
    661     /**
    662      * Returns an array of all joysticks installed on the system.
    663      *
    664      * @return an array of all joysticks installed on the system.
    665      */
    666     public Joystick[] getJoysticks() {
    667         return joysticks;
    668     }
    669 
    670     /**
    671      * Adds a {@link RawInputListener} to receive raw input events.
    672      *
    673      * <p>
    674      * Any raw input listeners registered to this <code>InputManager</code>
    675      * will receive raw input events first, before they get handled
    676      * by the <code>InputManager</code> itself. The listeners are
    677      * each processed in the order they were added, e.g. FIFO.
    678      * <p>
    679      * If a raw input listener has handled the event and does not wish
    680      * other listeners down the list to process the event, it may set the
    681      * {@link InputEvent#setConsumed() consumed flag} to indicate the
    682      * event was consumed and shouldn't be processed any further.
    683      * The listener may do this either at each of the event callbacks
    684      * or at the {@link RawInputListener#endInput() } method.
    685      *
    686      * @param listener A listener to receive raw input events.
    687      *
    688      * @see RawInputListener
    689      */
    690     public void addRawInputListener(RawInputListener listener) {
    691         rawListeners.add(listener);
    692         rawListenerArray = null;
    693     }
    694 
    695     /**
    696      * Removes a {@link RawInputListener} so that it no longer
    697      * receives raw input events.
    698      *
    699      * @param listener The listener to cease receiving raw input events.
    700      *
    701      * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
    702      */
    703     public void removeRawInputListener(RawInputListener listener) {
    704         rawListeners.remove(listener);
    705         rawListenerArray = null;
    706     }
    707 
    708     /**
    709      * Clears all {@link RawInputListener}s.
    710      *
    711      * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
    712      */
    713     public void clearRawInputListeners() {
    714         rawListeners.clear();
    715         rawListenerArray = null;
    716     }
    717 
    718     private RawInputListener[] getRawListenerArray() {
    719         if (rawListenerArray == null)
    720             rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]);
    721         return rawListenerArray;
    722     }
    723 
    724     /**
    725      * Enable simulation of mouse events. Used for touchscreen input only.
    726      *
    727      * @param value True to enable simulation of mouse events
    728      */
    729     public void setSimulateMouse(boolean value) {
    730         if (touch != null) {
    731             touch.setSimulateMouse(value);
    732         }
    733     }
    734     /**
    735      * Returns state of simulation of mouse events. Used for touchscreen input only.
    736      *
    737      */
    738     public boolean getSimulateMouse() {
    739         if (touch != null) {
    740             return touch.getSimulateMouse();
    741         } else {
    742             return false;
    743         }
    744     }
    745 
    746     /**
    747      * Enable simulation of keyboard events. Used for touchscreen input only.
    748      *
    749      * @param value True to enable simulation of keyboard events
    750      */
    751     public void setSimulateKeyboard(boolean value) {
    752         if (touch != null) {
    753             touch.setSimulateKeyboard(value);
    754         }
    755     }
    756 
    757     private void processQueue() {
    758         int queueSize = inputQueue.size();
    759         RawInputListener[] array = getRawListenerArray();
    760 
    761         for (RawInputListener listener : array) {
    762             listener.beginInput();
    763 
    764             for (int j = 0; j < queueSize; j++) {
    765                 InputEvent event = inputQueue.get(j);
    766                 if (event.isConsumed()) {
    767                     continue;
    768                 }
    769 
    770                 if (event instanceof MouseMotionEvent) {
    771                     listener.onMouseMotionEvent((MouseMotionEvent) event);
    772                 } else if (event instanceof KeyInputEvent) {
    773                     listener.onKeyEvent((KeyInputEvent) event);
    774                 } else if (event instanceof MouseButtonEvent) {
    775                     listener.onMouseButtonEvent((MouseButtonEvent) event);
    776                 } else if (event instanceof JoyAxisEvent) {
    777                     listener.onJoyAxisEvent((JoyAxisEvent) event);
    778                 } else if (event instanceof JoyButtonEvent) {
    779                     listener.onJoyButtonEvent((JoyButtonEvent) event);
    780                 } else if (event instanceof TouchEvent) {
    781                     listener.onTouchEvent((TouchEvent) event);
    782                 } else {
    783                     assert false;
    784                 }
    785             }
    786 
    787             listener.endInput();
    788         }
    789 
    790         for (int i = 0; i < queueSize; i++) {
    791             InputEvent event = inputQueue.get(i);
    792             if (event.isConsumed()) {
    793                 continue;
    794             }
    795 
    796             if (event instanceof MouseMotionEvent) {
    797                 onMouseMotionEventQueued((MouseMotionEvent) event);
    798             } else if (event instanceof KeyInputEvent) {
    799                 onKeyEventQueued((KeyInputEvent) event);
    800             } else if (event instanceof MouseButtonEvent) {
    801                 onMouseButtonEventQueued((MouseButtonEvent) event);
    802             } else if (event instanceof JoyAxisEvent) {
    803                 onJoyAxisEventQueued((JoyAxisEvent) event);
    804             } else if (event instanceof JoyButtonEvent) {
    805                 onJoyButtonEventQueued((JoyButtonEvent) event);
    806             } else if (event instanceof TouchEvent) {
    807                 onTouchEventQueued((TouchEvent) event);
    808             } else {
    809                 assert false;
    810             }
    811             // larynx, 2011.06.10 - flag event as reusable because
    812             // the android input uses a non-allocating ringbuffer which
    813             // needs to know when the event is not anymore in inputQueue
    814             // and therefor can be reused.
    815             event.setConsumed();
    816         }
    817 
    818         inputQueue.clear();
    819     }
    820 
    821     /**
    822      * Updates the <code>InputManager</code>.
    823      * This will query current input devices and send
    824      * appropriate events to registered listeners.
    825      *
    826      * @param tpf Time per frame value.
    827      */
    828     public void update(float tpf) {
    829         frameTPF = tpf;
    830 
    831         // Activate safemode if the TPF value is so small
    832         // that rounding errors are inevitable
    833         safeMode = tpf < 0.015f;
    834 
    835         long currentTime = keys.getInputTimeNanos();
    836         frameDelta = currentTime - lastUpdateTime;
    837 
    838         eventsPermitted = true;
    839 
    840         keys.update();
    841         mouse.update();
    842         if (joystick != null) {
    843             joystick.update();
    844         }
    845         if (touch != null) {
    846             touch.update();
    847         }
    848 
    849         eventsPermitted = false;
    850 
    851         processQueue();
    852         invokeUpdateActions();
    853 
    854         lastLastUpdateTime = lastUpdateTime;
    855         lastUpdateTime = currentTime;
    856     }
    857 
    858     /**
    859      * Dispatches touch events to touch listeners
    860      * @param evt The touch event to be dispatched to all onTouch listeners
    861      */
    862     public void onTouchEventQueued(TouchEvent evt) {
    863         ArrayList<Mapping> maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode()));
    864         if (maps == null) {
    865             return;
    866         }
    867 
    868         int size = maps.size();
    869         for (int i = size - 1; i >= 0; i--) {
    870             Mapping mapping = maps.get(i);
    871             ArrayList<InputListener> listeners = mapping.listeners;
    872             int listenerSize = listeners.size();
    873             for (int j = listenerSize - 1; j >= 0; j--) {
    874                 InputListener listener = listeners.get(j);
    875                 if (listener instanceof TouchListener) {
    876                     ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF);
    877                 }
    878             }
    879         }
    880     }
    881 
    882     /**
    883      * Callback from RawInputListener. Do not use.
    884      */
    885     @Override
    886     public void onTouchEvent(TouchEvent evt) {
    887         if (!eventsPermitted) {
    888             throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time.");
    889         }
    890         inputQueue.add(evt);
    891     }
    892 }
    893