Răsfoiți Sursa

Adding Android Sensor support as simulated joysticks. Only orientation is supported right now, more sensor types to be added later. When device orientation changes, Joystick[0] is updated just like using an actual joystick. Users need to add "joystickEventsEnabled = true" to the MainActivity to enable the orientation joystick so battery life is conserved if sensor data is not desired. See http://jmonkeyengine.org/groups/android/forum/topic/creating-engine-support-for-android-sensor-input/ for the long history.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9700 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
iwg..om 13 ani în urmă
părinte
comite
39f265f50e

+ 32 - 0
engine/src/android/com/jme3/app/AndroidHarness.java

@@ -15,7 +15,9 @@ import android.widget.ImageView;
 import android.widget.TextView;
 import com.jme3.audio.AudioRenderer;
 import com.jme3.audio.android.AndroidAudioRenderer;
+import com.jme3.input.JoyInput;
 import com.jme3.input.TouchInput;
+import com.jme3.input.android.AndroidSensorJoyInput;
 import com.jme3.input.controls.TouchListener;
 import com.jme3.input.controls.TouchTrigger;
 import com.jme3.input.event.TouchEvent;
@@ -61,6 +63,13 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
      */
     protected boolean eglConfigVerboseLogging = false;
 
+    /**
+     * If true Android Sensors are used as simulated Joysticks
+     * Users can use the Android sensor feedback through the RawInputListener
+     * or by registering JoyAxisTriggers.
+     */
+    protected boolean joystickEventsEnabled = false;
+
     /**
      * If true MouseEvents are generated from TouchEvents
      */
@@ -215,6 +224,9 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
                 logger.log(Level.WARNING, "Resolution from Window: {0}, {1}", new Object[]{disp.getWidth(), disp.getHeight()});
                 ctx.getSettings().setResolution(disp.getWidth(), disp.getHeight());
 
+                settings.setUseJoysticks(joystickEventsEnabled);
+                ctx.getSettings().setUseJoysticks(joystickEventsEnabled);
+
                 // AndroidHarness wraps the app as a SystemListener.
                 ctx.setSystemListener(this);
                 layoutDisplay();
@@ -257,6 +269,16 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
                     renderer.resumeAll();
                 }
             }
+            //resume the sensors (aka joysticks)
+            if (app.getContext() != null) {
+                JoyInput joyInput = app.getContext().getJoyInput();
+                if (joyInput != null) {
+                    if (joyInput instanceof AndroidSensorJoyInput) {
+                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
+                        androidJoyInput.resumeSensors();
+                    }
+                }
+            }
         }
 
         isGLThreadPaused = false;
@@ -280,6 +302,16 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
                     renderer.pauseAll();
                 }
             }
+            //pause the sensors (aka joysticks)
+            if (app.getContext() != null) {
+                JoyInput joyInput = app.getContext().getJoyInput();
+                if (joyInput != null) {
+                    if (joyInput instanceof AndroidSensorJoyInput) {
+                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
+                        androidJoyInput.pauseSensors();
+                    }
+                }
+            }
         }
         isGLThreadPaused = true;
         logger.info("onPause");

+ 658 - 0
engine/src/android/com/jme3/input/android/AndroidSensorJoyInput.java

@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) 2009-2010 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.android;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+import com.jme3.input.InputManager;
+import com.jme3.input.JoyInput;
+import com.jme3.input.Joystick;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.math.FastMath;
+import com.jme3.system.android.JmeAndroidSystem;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author iwgeric
+ */
+public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
+    private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName());
+
+    private InputManager inputManager = null;
+    private SensorManager sensorManager = null;
+    private RawInputListener listener = null;
+    private IntMap<SensorData> sensors = new IntMap<SensorData>();
+    private Joystick[] joysticks;
+    private boolean initialized = false;
+    private WindowManager window;
+    private Display disp;
+    private int lastRotation = 0;
+    private final float[] orientationLastValues = new float[3];
+    private final float[] maxOrientationValues = new float[] {FastMath.HALF_PI, FastMath.HALF_PI, FastMath.HALF_PI};
+
+    private final ArrayList<JoyAxisEvent> eventQueue = new ArrayList<JoyAxisEvent>();
+
+    /**
+     * Internal class to enclose data for each sensor.
+     */
+    private class SensorData {
+        int androidSensorType = -1;
+        int androidSensorSpeed = SensorManager.SENSOR_DELAY_GAME;
+        Sensor sensor = null;
+        float maxRange = 0f;
+        float resolution = 0f;
+        float[] lastValues;
+        final Object valuesLock = new Object();
+        int joyID = -1;
+        String joyName = "";
+        boolean enabled = false;
+        boolean haveData = false;
+        boolean createJoystick = false;
+
+        public SensorData(int androidSensorType, Sensor sensor) {
+            this.androidSensorType = androidSensorType;
+            this.sensor = sensor;
+        }
+
+    }
+
+    private void initSensorManager() {
+        initWindow();
+        sensorManager = (SensorManager) JmeAndroidSystem.getActivity().getSystemService(Context.SENSOR_SERVICE);
+        initSensors();
+    }
+
+    /**
+     * Used internally.  Do not use.
+     * Allows the context to reset the current activity for getting device rotation
+     */
+    public void initWindow() {
+        window = JmeAndroidSystem.getActivity().getWindowManager();
+        disp = window.getDefaultDisplay();
+    }
+
+    private void initSensors() {
+        SensorData sensorData;
+
+        List<Sensor> availSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+        for (Sensor sensor: availSensors) {
+            logger.log(Level.INFO, "{0} Sensor is available, Type: {1}, Vendor: {2}, Version: {3}",
+                    new Object[]{sensor.getName(), sensor.getType(), sensor.getVendor(), sensor.getVersion()});
+        }
+
+        sensorData = initSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        if (sensorData != null) {
+            sensorData.joyName = "Device Direction";
+            sensorData.lastValues = new float[3];
+            sensorData.createJoystick = false;
+        }
+
+        sensorData = initSensor(Sensor.TYPE_ACCELEROMETER);
+        if (sensorData != null) {
+            sensorData.joyName = "Device Acceleration";
+            sensorData.lastValues = new float[3];
+            sensorData.createJoystick = false;
+        }
+
+//        sensorData = initSensor(Sensor.TYPE_GYROSCOPE);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Rotation";
+//            sensorData.lastValues = new float[3];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_GRAVITY);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Gravity";
+//            sensorData.lastValues = new float[3];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_LINEAR_ACCELERATION);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Linear Acceleration";
+//            sensorData.lastValues = new float[3];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_ROTATION_VECTOR);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Rotation Vector";
+//            sensorData.lastValues = new float[4];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_PROXIMITY);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Proximity";
+//            sensorData.lastValues = new float[1];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_LIGHT);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Light";
+//            sensorData.lastValues = new float[1];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_PRESSURE);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Pressure";
+//            sensorData.lastValues = new float[1];
+//            sensorData.createJoystick = false;
+//        }
+//
+//        sensorData = initSensor(Sensor.TYPE_TEMPERATURE);
+//        if (sensorData != null) {
+//            sensorData.joyName = "Device Temperature";
+//            sensorData.lastValues = new float[1];
+//            sensorData.createJoystick = false;
+//        }
+
+    }
+
+    private SensorData initSensor(int sensorType) {
+        boolean success = false;
+
+        SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null) {
+            unRegisterListener(sensorType);
+        } else {
+            sensorData = new SensorData(sensorType, null);
+            sensors.put(sensorType, sensorData);
+        }
+
+        sensorData.androidSensorType = sensorType;
+        sensorData.sensor = sensorManager.getDefaultSensor(sensorType);
+
+        if (sensorData.sensor != null) {
+            logger.log(Level.INFO, "Sensor Type {0} found.", sensorType);
+            success = registerListener(sensorType);
+        } else {
+            logger.log(Level.INFO, "Sensor Type {0} not found.", sensorType);
+        }
+
+        if (success) {
+            return sensorData;
+        } else {
+            return null;
+        }
+    }
+
+    private boolean registerListener(int sensorType) {
+        SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null) {
+            if (sensorData.enabled) {
+                logger.log(Level.INFO, "Sensor Already Active: SensorType: {0}, active: {1}",
+                        new Object[]{sensorType, sensorData.enabled});
+                return true;
+            }
+            sensorData.haveData = false;
+            if (sensorData.sensor != null) {
+                if (sensorManager.registerListener(this, sensorData.sensor, sensorData.androidSensorSpeed)) {
+                    sensorData.enabled = true;
+                    logger.log(Level.INFO, "SensorType: {0}, actived: {1}",
+                            new Object[]{sensorType, sensorData.enabled});
+                    return true;
+                } else {
+                    sensorData.enabled = false;
+                    logger.log(Level.INFO, "Sensor Type {0} activation failed.", sensorType);
+                }
+            }
+        }
+        return false;
+    }
+
+    private void unRegisterListener(int sensorType) {
+        SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null) {
+            if (sensorData.sensor != null) {
+                sensorManager.unregisterListener(this, sensorData.sensor);
+            }
+            sensorData.enabled = false;
+            sensorData.haveData = false;
+            logger.log(Level.INFO, "SensorType: {0} deactivated, active: {1}",
+                    new Object[]{sensorType, sensorData.enabled});
+        }
+    }
+
+    /**
+     * Pauses the sensors to save battery life if the sensors are not needed.
+     * Used to pause sensors when the activity pauses
+     */
+    public void pauseSensors() {
+        for (Entry entry: sensors) {
+            unRegisterListener(entry.getKey());
+        }
+    }
+
+    /**
+     * Resumes the sensors.
+     * Used to resume sensors when the activity comes to the top of the stack
+     */
+    public void resumeSensors() {
+        for (Entry entry: sensors) {
+            registerListener(entry.getKey());
+        }
+    }
+
+    /*
+     * Allows the orientation data to be rotated based on the current device
+     * rotation.  This keeps the data aligned with the game when the user
+     * rotates the device during game play.
+     *
+     * Android remapCoordinateSystem from the Android docs
+     * remapCoordinateSystem(float[] inR, int X, int Y, float[] outR)
+     *
+     * @param   inR   the rotation matrix to be transformed. Usually it is the matrix
+     *          returned by getRotationMatrix(float[], float[], float[], float[]).
+     *
+     * @param   outR  the transformed rotation matrix. inR and outR can be the same
+     *          array, but it is not recommended for performance reason.
+     *
+     * X     defines on which world (Earth) axis and direction the X axis of the device is mapped.
+     * Y     defines on which world (Earth) axis and direction the Y axis of the device is mapped.
+     *
+     * @return True if successful
+     */
+    private boolean remapCoordinates(float[] inR, float[] outR) {
+        int xDir = SensorManager.AXIS_X;
+        int yDir = SensorManager.AXIS_Y;
+        int curRotation = getScreenRotation();
+        if (lastRotation != curRotation) {
+            logger.log(Level.INFO, "Device Rotation changed to: {0}", curRotation);
+        }
+        lastRotation = curRotation;
+
+//        logger.log(Level.INFO, "Screen Rotation: {0}", getScreenRotation());
+        switch (getScreenRotation()) {
+            // device natural position
+            case Surface.ROTATION_0:
+                xDir = SensorManager.AXIS_X;
+                yDir = SensorManager.AXIS_Y;
+                break;
+            // device rotated 90 deg counterclockwise
+            case Surface.ROTATION_90:
+                xDir = SensorManager.AXIS_Y;
+                yDir = SensorManager.AXIS_MINUS_X;
+                break;
+            // device rotated 180 deg counterclockwise
+            case Surface.ROTATION_180:
+                xDir = SensorManager.AXIS_MINUS_X;
+                yDir = SensorManager.AXIS_MINUS_Y;
+                break;
+            // device rotated 270 deg counterclockwise
+            case Surface.ROTATION_270:
+                xDir = SensorManager.AXIS_MINUS_Y;
+                yDir = SensorManager.AXIS_X;
+                break;
+            default:
+                break;
+        }
+        return SensorManager.remapCoordinateSystem(inR, xDir, yDir, outR);
+    }
+
+    /**
+     * Returns the current device rotation.
+     * Surface.ROTATION_0 = device in natural default rotation
+     * Surface.ROTATION_90 = device in rotated 90deg counterclockwise
+     * Surface.ROTATION_180 = device in rotated 180deg counterclockwise
+     * Surface.ROTATION_270 = device in rotated 270deg counterclockwise
+     *
+     * When the Manifest locks the orientation, this value will not change during
+     * gametime, but if the orientation of the screen is based off the sensor,
+     * this value will change as the device is rotated.
+     * @return Current device rotation amount
+     */
+    private int getScreenRotation() {
+        return disp.getRotation();
+    }
+
+    /**
+     * Calculates the device orientation based off the data recieved from the
+     * Acceleration Sensor and Mangetic Field sensor
+     * Values are returned relative to the Earth.
+     *
+     * From the Android Doc
+     *
+     * Computes the device's orientation based on the rotation matrix. When it returns, the array values is filled with the result:
+     *  values[0]: azimuth, rotation around the Z axis.
+     *  values[1]: pitch, rotation around the X axis.
+     *  values[2]: roll, rotation around the Y axis.
+     *
+     * The reference coordinate-system used is different from the world
+     * coordinate-system defined for the rotation matrix:
+     *  X is defined as the vector product Y.Z (It is tangential to the ground at the device's current location and roughly points West).
+     *  Y is tangential to the ground at the device's current location and points towards the magnetic North Pole.
+     *  Z points towards the center of the Earth and is perpendicular to the ground.
+     *
+     * @return True if Orientation was calculated
+     */
+    private boolean updateOrientation() {
+        SensorData sensorData;
+        final float[] curInclinationMat = new float[16];
+        final float[] curRotationMat = new float[16];
+        final float[] rotatedRotationMat = new float[16];
+        final float[] accValues = new float[3];
+        final float[] magValues = new float[3];
+        final float[] deltaOrientation = new float[3];
+
+        // if the Gravity Sensor is available, use it for orientation, if not
+        // use the accelerometer
+        // NOTE: Seemed to work worse, so just using accelerometer
+//        sensorData = sensors.get(Sensor.TYPE_GRAVITY);
+//        if (sensorData == null) {
+            sensorData = sensors.get(Sensor.TYPE_ACCELEROMETER);
+//        }
+
+        if (sensorData == null || !sensorData.enabled || !sensorData.haveData) {
+            return false;
+        }
+
+        synchronized(sensorData.valuesLock) {
+            accValues[0] = sensorData.lastValues[0];
+            accValues[1] = sensorData.lastValues[1];
+            accValues[2] = sensorData.lastValues[2];
+        }
+
+        sensorData = sensors.get(Sensor.TYPE_MAGNETIC_FIELD);
+        if (sensorData == null || !sensorData.enabled || !sensorData.haveData) {
+            return false;
+        }
+
+        synchronized(sensorData.valuesLock) {
+            magValues[0] = sensorData.lastValues[0];
+            magValues[1] = sensorData.lastValues[1];
+            magValues[2] = sensorData.lastValues[2];
+        }
+
+        if (SensorManager.getRotationMatrix(curRotationMat, curInclinationMat, accValues, magValues)) {
+            final float [] orientValues = new float[3];
+            if (remapCoordinates(curRotationMat, rotatedRotationMat)) {
+                SensorManager.getOrientation(rotatedRotationMat, orientValues);
+//                logger.log(Level.INFO, "Orientation Values: {0}, {1}, {2}",
+//                        new Object[]{orientValues[0], orientValues[1], orientValues[2]});
+
+                //values[0]: Azimuth - (the compass bearing east of magnetic north)
+                //values[1]: Pitch, rotation around x-axis (is the phone leaning forward or back)
+                //values[2]: Roll, rotation around y-axis (is the phone leaning over on its left or right side)
+
+                //Azimuth (degrees of rotation around the z axis). This is the angle between magnetic north
+                //and the device's y axis. For example, if the device's y axis is aligned with magnetic north
+                //this value is 0, and if the device's y axis is pointing south this value is 180.
+                //Likewise, when the y axis is pointing east this value is 90 and when it is pointing west
+                //this value is 270.
+
+                //Pitch (degrees of rotation around the x axis). This value is positive when the positive
+                //z axis rotates toward the positive y axis, and it is negative when the positive z axis
+                //rotates toward the negative y axis. The range of values is 180 degrees to -180 degrees.
+
+                //Roll (degrees of rotation around the y axis). This value is positive when the
+                //positive z axis rotates toward the positive x axis, and it is negative when the
+                //positive z axis rotates toward the negative x axis. The range of values
+                //is 90 degrees to -90 degrees.
+
+
+//                // Azimuth scaling
+//                if (orientValues[0]<0) {
+//                    orientValues[0] += FastMath.TWO_PI;
+//                }
+//
+//                // Pitch scaling
+//                if (orientValues[1] < -FastMath.HALF_PI) {
+//                    orientValues[1] += (-2*(FastMath.HALF_PI+orientValues[1]));
+//                } else if (orientValues[1] > FastMath.HALF_PI) {
+//                    orientValues[1] += (2*(FastMath.HALF_PI-orientValues[1]));
+//                }
+//
+//                // Roll scaling
+//                // NOT NEEDED
+
+                // need to reorder to make it x, y, z order instead of z, x, y order
+                deltaOrientation[0] = orientValues[1] - orientationLastValues[1];
+                deltaOrientation[1] = orientValues[2] - orientationLastValues[2];
+                deltaOrientation[2] = orientValues[0] - orientationLastValues[0];
+
+                synchronized (eventQueue){
+                    // only send data to inputManager if it is different than last time
+                    // orientValues[1] is the X axis -> JoyAxisEvent Axis 0
+                    // orientValues[2] is the Y axis -> JoyAxisEvent Axis 1
+                    // orientValues[0] is the Z axis -> JoyAxisEvent Axis 2
+                    if (Math.abs(deltaOrientation[0]) > FastMath.ZERO_TOLERANCE) {
+                        eventQueue.add(new JoyAxisEvent(0, 0, orientValues[1] / maxOrientationValues[1]));
+                    }
+                    if (Math.abs(deltaOrientation[1]) > FastMath.ZERO_TOLERANCE) {
+                        eventQueue.add(new JoyAxisEvent(0, 1, orientValues[2] / maxOrientationValues[2]));
+                    }
+                    if (Math.abs(deltaOrientation[2]) > FastMath.ZERO_TOLERANCE) {
+                        eventQueue.add(new JoyAxisEvent(0, 2, orientValues[0] / maxOrientationValues[0]));
+                    }
+                }
+
+                orientationLastValues[0] = orientValues[0];
+                orientationLastValues[1] = orientValues[1];
+                orientationLastValues[2] = orientValues[2];
+
+                return true;
+            } else {
+                //logger.log(Level.INFO, "remapCoordinateSystem failed");
+            }
+
+        } else {
+            //logger.log(Level.INFO, "getRotationMatrix returned false");
+        }
+
+        return false;
+    }
+
+    // Start of JoyInput methods
+
+    public void setJoyRumble(int joyId, float amount) {
+        return;
+    }
+
+    public Joystick[] loadJoysticks(InputManager inputManager) {
+        this.inputManager = inputManager;
+
+        int joyIndex = 1;  // start with 1 for orientation
+        for (Entry entry: sensors) {
+            SensorData sensorData = (SensorData)entry.getValue();
+            if (sensorData != null) {
+                if (sensorData.sensor != null && sensorData.createJoystick) {
+                    joyIndex++; // add 1 for each of the physical sensors configured and enabled
+
+                }
+            }
+        }
+
+        joysticks = new Joystick[joyIndex];
+        joyIndex = 0;
+        Joystick joystick;
+
+        // manually create a joystick for orientation since orientation
+        // is not an actual physical sensor
+        // Do the orientation joystick first so it is compatible with PC systems
+        // that only have a single joystick configured.
+        joystick = new Joystick(inputManager,
+                                    this,
+                                    joyIndex,
+                                    "Device Orientation",
+                                    0, // button count
+                                    3, // axis count
+                                    0, 1); // xAxis, yAxis
+        joysticks[joyIndex] = joystick;
+        joyIndex++;
+
+        // create a joystick for each physical sensor configured
+        for (Entry entry: sensors) {
+            SensorData sensorData = (SensorData)entry.getValue();
+            if (sensorData != null) {
+                if (sensorData.sensor != null && sensorData.createJoystick) {
+                    sensorData.joyID = joyIndex;
+                    joystick = new Joystick(inputManager,
+                                                this,
+                                                joyIndex,
+                                                sensorData.joyName,
+                                                0, // button count
+                                                sensorData.lastValues.length, // axis count
+                                                0, 1); // xAxis, yAxis
+                    joysticks[joyIndex] = joystick;
+                    joyIndex++;
+
+                }
+            }
+        }
+
+        return joysticks;
+    }
+
+    public void initialize() {
+        logger.log(Level.INFO, "Doing Initialize.");
+        initSensorManager();
+        initialized = true;
+    }
+
+    public void update() {
+        updateOrientation();
+        synchronized (eventQueue){
+            // flush events to listener
+            if (listener != null && eventQueue.size() > 0) {
+                for (int i = 0; i < eventQueue.size(); i++){
+                    listener.onJoyAxisEvent(eventQueue.get(i));
+                }
+                eventQueue.clear();
+            }
+        }
+    }
+
+    public void destroy() {
+        logger.log(Level.INFO, "Doing Destroy.");
+        pauseSensors();
+        if (sensorManager != null) {
+            sensorManager.unregisterListener(this);
+        }
+        sensors.clear();
+        eventQueue.clear();
+        joysticks = null;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    // End of JoyInput methods
+
+    // Start of Android SensorEventListener methods
+
+    public void onSensorChanged(SensorEvent se) {
+        if (!initialized) {
+            return;
+        }
+
+        int sensorType = se.sensor.getType();
+
+        SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) {
+
+            if (!sensorData.haveData) {
+                sensorData.haveData = true;
+
+            } else {
+                if (sensorData.joyID != -1) {
+                    final float[] deltaValues = new float[sensorData.lastValues.length];
+                    for (int i=0; i<sensorData.lastValues.length; i++) {
+                        deltaValues[i] = se.values[i] - sensorData.lastValues[i];
+                    }
+
+                    // TODO: need to scale physical sensor data to fit within
+                    // joystick model of providing values of 0 to 1
+                    synchronized (eventQueue){
+                        for (int i=0; i<deltaValues.length; i++) {
+                            if (FastMath.abs(deltaValues[i]) > sensorData.lastValues[i] + FastMath.ZERO_TOLERANCE) {
+                                eventQueue.add(new JoyAxisEvent(sensorData.joyID, i, se.values[i]));
+                            }
+                        }
+                    }
+                }
+
+            }
+
+            synchronized(sensorData.valuesLock) {
+                for (int i=0; i<sensorData.lastValues.length; i++) {
+                    sensorData.lastValues[i] = se.values[i];
+                }
+            }
+
+        }
+    }
+
+    public void onAccuracyChanged(Sensor sensor, int i) {
+        int sensorType = sensor.getType();
+        SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null) {
+            logger.log(Level.INFO, "onAccuracyChanged for {0} ({1}): accuracy: {2}",
+                    new Object[]{sensor.toString(), sensorData.joyName, i});
+            logger.log(Level.INFO, "MaxRange: {0}, Resolution: {1}",
+                    new Object[]{sensor.getMaximumRange(), sensor.getResolution()});
+            if (sensorData.sensor != null && sensorData.sensor.equals(sensor)) {
+                sensorData.resolution = sensor.getResolution();
+                sensorData.maxRange = sensor.getMaximumRange();
+            }
+        }
+    }
+
+    // End of SensorEventListener methods
+
+}

+ 12 - 1
engine/src/android/com/jme3/system/android/OGLESContext.java

@@ -43,6 +43,7 @@ import android.widget.EditText;
 import android.widget.FrameLayout;
 import com.jme3.input.*;
 import com.jme3.input.android.AndroidInput;
+import com.jme3.input.android.AndroidSensorJoyInput;
 import com.jme3.input.controls.SoftTextDialogInputListener;
 import com.jme3.input.dummy.DummyKeyInput;
 import com.jme3.input.dummy.DummyMouseInput;
@@ -76,6 +77,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
     protected AndroidInput androidInput;
     protected AndroidGLSurfaceView view;
     protected int minFrameDuration = 0;                   // No FPS cap
+    protected JoyInput androidSensorJoyInput = null;
     /**
      * EGL_RENDERABLE_TYPE: EGL_OPENGL_ES_BIT = OpenGL ES 1.0 |
      * EGL_OPENGL_ES2_BIT = OpenGL ES 2.0
@@ -106,6 +108,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
      * @return GLSurfaceView The newly created view
      */
     public GLSurfaceView createView(ConfigType configType, boolean eglConfigVerboseLogging) {
+        // if simulated joysticks are used, init the window to update the activity used to
+        // get the window orientation
+        if (androidSensorJoyInput != null && androidSensorJoyInput instanceof AndroidSensorJoyInput) {
+            ((AndroidSensorJoyInput)androidSensorJoyInput).initWindow();
+        }
+
         // Start to set up the view
         view = new AndroidGLSurfaceView(JmeAndroidSystem.getActivity());
         if (androidInput == null) {
@@ -268,7 +276,10 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
 
     @Override
     public JoyInput getJoyInput() {
-        return null;
+        if (androidSensorJoyInput == null) {
+            androidSensorJoyInput = new AndroidSensorJoyInput();
+        }
+        return androidSensorJoyInput;
     }
 
     @Override

+ 28 - 19
engine/src/core/com/jme3/input/Joystick.java

@@ -5,7 +5,7 @@ import com.jme3.input.controls.JoyButtonTrigger;
 
 /**
  * A joystick represents a single joystick that is installed in the system.
- * 
+ *
  * @author Kirill Vainer
  */
 public final class Joystick {
@@ -37,7 +37,7 @@ public final class Joystick {
 
     /**
      * Rumbles the joystick for the given amount/magnitude.
-     * 
+     *
      * @param amount The amount to rumble. Should be between 0 and 1.
      */
     public void rumble(float amount){
@@ -47,11 +47,11 @@ public final class Joystick {
     /**
      * Assign the mapping name to receive events from the given button index
      * on the joystick.
-     * 
+     *
      * @param mappingName The mapping to receive joystick button events.
      * @param buttonId The button index.
-     * 
-     * @see Joystick#getButtonCount() 
+     *
+     * @see Joystick#getButtonCount()
      */
     public void assignButton(String mappingName, int buttonId){
         if (buttonId < 0 || buttonId >= buttonCount)
@@ -62,12 +62,12 @@ public final class Joystick {
 
     /**
      * Assign the mappings to receive events from the given joystick axis.
-     * 
+     *
      * @param positiveMapping The mapping to receive events when the axis is negative
      * @param negativeMapping The mapping to receive events when the axis is positive
      * @param axisId The axis index.
-     * 
-     * @see Joystick#getAxisCount() 
+     *
+     * @see Joystick#getAxisCount()
      */
     public void assignAxis(String positiveMapping, String negativeMapping, int axisId){
         inputManager.addMapping(positiveMapping, new JoyAxisTrigger(joyId, axisId, false));
@@ -76,12 +76,12 @@ public final class Joystick {
 
     /**
      * Gets the index number for the X axis on the joystick.
-     * 
+     *
      * <p>E.g. for most gamepads, the left control stick X axis will be returned.
-     * 
+     *
      * @return The axis index for the X axis for this joystick.
-     * 
-     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) 
+     *
+     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int)
      */
     public int getXAxisIndex(){
         return axisXIndex;
@@ -89,12 +89,12 @@ public final class Joystick {
 
     /**
      * Gets the index number for the Y axis on the joystick.
-     * 
+     *
      * <p>E.g. for most gamepads, the left control stick Y axis will be returned.
-     * 
+     *
      * @return The axis index for the Y axis for this joystick.
-     * 
-     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) 
+     *
+     * @see Joystick#assignAxis(java.lang.String, java.lang.String, int)
      */
     public int getYAxisIndex(){
         return axisYIndex;
@@ -102,7 +102,7 @@ public final class Joystick {
 
     /**
      * Returns the number of axes on this joystick.
-     * 
+     *
      * @return the number of axes on this joystick.
      */
     public int getAxisCount() {
@@ -111,7 +111,7 @@ public final class Joystick {
 
     /**
      * Returns the number of buttons on this joystick.
-     * 
+     *
      * @return the number of buttons on this joystick.
      */
     public int getButtonCount() {
@@ -120,13 +120,22 @@ public final class Joystick {
 
     /**
      * Returns the name of this joystick.
-     * 
+     *
      * @return the name of this joystick.
      */
     public String getName() {
         return name;
     }
 
+    /**
+     * Returns the joyId of this joystick.
+     *
+     * @return the joyId of this joystick.
+     */
+    public int getJoyId() {
+        return joyId;
+    }
+
     @Override
     public String toString(){
         return "Joystick[name=" + name + ", id=" + joyId + ", buttons=" + buttonCount