diff --git a/engine/src/android/com/jme3/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java index cba95303f..08c0fe176 100644 --- a/engine/src/android/com/jme3/app/AndroidHarness.java +++ b/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.SensorInput; import com.jme3.input.TouchInput; +import com.jme3.input.android.AndroidSensorInput; import com.jme3.input.controls.TouchListener; import com.jme3.input.controls.TouchTrigger; import com.jme3.input.event.TouchEvent; @@ -39,64 +41,64 @@ import java.util.logging.Logger; public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener { protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); - + /** * The application class to start */ protected String appClass = "jme3test.android.Test"; - + /** * The jme3 application object */ protected Application app = null; - + /** * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is * RGBA8888 or better if supported by the hardware */ protected ConfigType eglConfigType = ConfigType.FASTEST; - + /** * If true all valid and not valid egl configs are logged */ protected boolean eglConfigVerboseLogging = false; - + /** * If true MouseEvents are generated from TouchEvents */ protected boolean mouseEventsEnabled = true; - + /** * Flip X axis */ protected boolean mouseEventsInvertX = true; - + /** * Flip Y axis */ protected boolean mouseEventsInvertY = true; - + /** * if true finish this activity when the jme app is stopped */ protected boolean finishOnAppStop = true; - + /** * set to false if you don't want the harness to handle the exit hook */ protected boolean handleExitHook = true; - + /** * Title of the exit dialog, default is "Do you want to exit?" */ protected String exitDialogTitle = "Do you want to exit?"; - + /** * Message of the exit dialog, default is "Use your home key to bring this * app into the background or exit to terminate it." */ protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; - + /** * Set the screen window mode. If screenFullSize is true, then the * notification bar and title bar are removed and the screen covers the @@ -105,20 +107,20 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt * false, then the title bar is also displayed under the notification bar. */ protected boolean screenFullScreen = true; - + /** * if screenShowTitle is true while screenFullScreen is false, then the * title bar is also displayed under the notification bar */ protected boolean screenShowTitle = true; - + /** * Splash Screen picture Resource ID. If a Splash Screen is desired, set * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If * splashPicID = 0, then no splash screen will be displayed. */ protected int splashPicID = 0; - + /** * Set the screen orientation, default is SENSOR * ActivityInfo.SCREEN_ORIENTATION_* constants package @@ -142,7 +144,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt private class DataObject { protected Application app = null; } - + @Override public Object onRetainNonConfigurationInstance() { logger.log(Level.INFO, "onRetainNonConfigurationInstance called"); @@ -156,7 +158,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt logger.log(Level.INFO, "inConfigChange: {0}", inConfigChange); return data; } - + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -257,6 +259,16 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt renderer.resumeAll(); } } + //resume the sensors + if (app.getInputManager() != null) { + SensorInput sensorInput = app.getInputManager().getSensorInput(); + if (sensorInput != null) { + logger.log(Level.INFO, "resume: {0}", sensorInput.getClass().getSimpleName()); + if (sensorInput instanceof AndroidSensorInput) { + ((AndroidSensorInput)sensorInput).resumeSensors(); + } + } + } } isGLThreadPaused = false; @@ -280,6 +292,16 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt renderer.pauseAll(); } } + //pause the sensors + if (app.getInputManager() != null) { + SensorInput sensorInput = app.getInputManager().getSensorInput(); + if (sensorInput != null) { + logger.log(Level.INFO, "pause: {0}", sensorInput.getClass().getSimpleName()); + if (sensorInput instanceof AndroidSensorInput) { + ((AndroidSensorInput)sensorInput).resumeSensors(); + } + } + } } isGLThreadPaused = true; logger.info("onPause"); @@ -406,7 +428,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt ((ViewGroup)view.getParent()).removeView(view); } frameLayout.addView(view); - + if (splashImageView.getParent() != null) { ((ViewGroup)splashImageView.getParent()).removeView(splashImageView); } diff --git a/engine/src/android/com/jme3/input/android/AndroidSensorInput.java b/engine/src/android/com/jme3/input/android/AndroidSensorInput.java new file mode 100644 index 000000000..fe389f459 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidSensorInput.java @@ -0,0 +1,584 @@ +/* + * 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.RawInputListener; +import com.jme3.input.SensorInput; +import com.jme3.input.event.MotionSensorEvent; +import com.jme3.math.Vector3f; +import com.jme3.system.android.JmeAndroidSystem; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Android specific implementation of SensorInput. + * + * @author iwgeric + */ +public class AndroidSensorInput implements SensorInput, SensorEventListener { + private final static Logger logger = Logger.getLogger(AndroidSensorInput.class.getName()); + + private SensorManager sensorManager = null; + private RawInputListener listener = null; + private Map sensors = new HashMap(); + private boolean initialized = false; + private WindowManager window; + private Display disp; + + private final float[] curAccValues = new float[3]; + private final float[] curMagValues = new float[3]; + private final float[] curInclination = new float[9]; + private final float[] curRotation = new float[9]; + private final float[] rotatedRotation = new float[9]; + + private final ArrayList eventQueue = new ArrayList(); + + /** + * Internal class to enclose data for each sensor. + */ + private class SensorData { + int androidSensorType = -1; + int androidSensorSpeed = SensorManager.SENSOR_DELAY_UI; + Sensor sensor = null; + Vector3f lastValues = new Vector3f(); + float minChangePercent = 0f; + boolean enabled = false; + boolean paused = false; + + public SensorData(int androidSensorType, Sensor sensor) { + this.androidSensorType = androidSensorType; + this.sensor = sensor; + } + } + + /** + * Pauses the active sensors to save battery. Mostly used internally so that + * the sensors can be deactivated while the game Activity is + * in the background to save battery life + */ + public void pauseSensors() { + for (Entry entry : sensors.entrySet()) { + SensorData sensorData = entry.getValue(); + if (sensorData.sensor != null) { + unRegisterListener(entry.getKey()); + sensorData.paused = true; + } + } + } + + /** + * Resumes paused sensors. Mostly used internally so that + * the sensors can be reactivated when the game Activity is + * placed back onto the forefront. + */ + public void resumeSensors() { + for (Entry entry : sensors.entrySet()) { + SensorData sensorData = entry.getValue(); + if (sensorData.sensor != null && sensorData.paused) { + if (registerListener(entry.getKey())) { + sensorData.paused = false; + } + } + } + } + + /** + * Used internally by the context to reset the Sensor Manager on device rotations. + * Necessary because a new Activity is created on a device rotation, so the + * Sensor Manager needs to be reset with the new Activity. + */ + public void resetSensorManager() { + initSensorManager(); + } + + private void initSensorManager() { + window = JmeAndroidSystem.getActivity().getWindowManager(); + disp = window.getDefaultDisplay(); + + sensorManager = (SensorManager) JmeAndroidSystem.getActivity().getSystemService(Context.SENSOR_SERVICE); + + initSensor(SensorInput.SENSOR_TYPE_MAGNETIC_FIELD); + initSensor(SensorInput.SENSOR_TYPE_ACCELEROMETER); + initSensor(SensorInput.SENSOR_TYPE_ORIENTATION); + + } + + private boolean initSensor(int sensorType) { + boolean result = false; + boolean previouslyActive = false; + + SensorData sensorData = sensors.get((Integer)sensorType); + if (sensorData != null) { + if (sensorData.enabled) { + previouslyActive = true; + } + unRegisterListener(sensorType); + } else { + sensorData = new SensorData(sensorType, null); + sensors.put(sensorType, sensorData); + } + + switch (sensorType) { + case SensorInput.SENSOR_TYPE_MAGNETIC_FIELD: + sensorData.androidSensorType = Sensor.TYPE_MAGNETIC_FIELD; + sensorData.sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + break; + case SensorInput.SENSOR_TYPE_ACCELEROMETER: + sensorData.androidSensorType = Sensor.TYPE_ACCELEROMETER; + sensorData.sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + break; + case SensorInput.SENSOR_TYPE_ORIENTATION: + sensorData.androidSensorType = Sensor.TYPE_ORIENTATION; + //Orientation is not a sensor anymore but rather a call to SensorMangaer + // to get the current orientation based on the Magnetic and Accelerometer sensor data + sensorData.sensor = null; + break; + + default: + throw new IllegalArgumentException("Invalid Sensor Type."); + } + + if (sensorData.sensor != null || sensorType == SensorInput.SENSOR_TYPE_ORIENTATION) { + logger.log(Level.INFO, "Sensor Type {0} found.", sensorType); + if (previouslyActive) { + logger.log(Level.INFO, "Reactivating Sensor Type {0}.", sensorType); + registerListener(sensorType); + } + result = true; + } + + + return result; + } + + private boolean registerListener(int sensorType) { + SensorData sensorData = sensors.get((Integer)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; + } + if (sensorData.sensor != null) { + if (sensorManager.registerListener(this, sensorData.sensor, sensorData.androidSensorSpeed)) { + sensorData.enabled = true; + logger.log(Level.INFO, "SensorType: {0}, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + logger.log(Level.INFO, "Sensor Type {0} activated.", sensorType); + return true; + } else { + sensorData.enabled = false; + logger.log(Level.INFO, "Sensor Type {0} activation failed.", sensorType); + } + } else if (sensorType == SensorInput.SENSOR_TYPE_ORIENTATION) { + logger.log(Level.INFO, "Sensor is Orientation"); + if (registerListener(SensorInput.SENSOR_TYPE_MAGNETIC_FIELD) && registerListener(SensorInput.SENSOR_TYPE_ACCELEROMETER)) { + sensorData.enabled = true; + logger.log(Level.INFO, "Magnetic and Acceleration Sensors Registered and Orientation Sensor being simulated."); + return true; + } + } + sensorData.lastValues = null; + } + return false; + } + + private void unRegisterListener(int sensorType) { + SensorData sensorData = sensors.get((Integer)sensorType); + if (sensorData != null) { + if (sensorData.sensor != null) { + sensorManager.unregisterListener(this, sensorData.sensor); + } else if (sensorType == SensorInput.SENSOR_TYPE_ORIENTATION) { + logger.log(Level.INFO, "Mangetic and Acceleration Sensors are being deactivated with Orientation Sensor."); + unRegisterListener(SensorInput.SENSOR_TYPE_MAGNETIC_FIELD); + unRegisterListener(SensorInput.SENSOR_TYPE_ACCELEROMETER); + } + sensorData.enabled = false; + logger.log(Level.INFO, "SensorType: {0}, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + logger.log(Level.INFO, "Sensor Type {0} deactivated.", sensorType); + } + } + + /* + * 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 axis and direction the X axis of the device is mapped. + * Y defines on which world 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; + +// logger.log(Level.INFO, "Screen Rotation: {0}", getScreenRotation()); + if (getScreenRotation() == Surface.ROTATION_0) { + xDir = SensorManager.AXIS_X; + yDir = SensorManager.AXIS_Y; + } + if (getScreenRotation() == Surface.ROTATION_90) { + xDir = SensorManager.AXIS_MINUS_Y; + yDir = SensorManager.AXIS_MINUS_X; + } + if (getScreenRotation() == Surface.ROTATION_180) { + xDir = SensorManager.AXIS_MINUS_X; + yDir = SensorManager.AXIS_MINUS_Y; + } + if (getScreenRotation() == Surface.ROTATION_270) { + xDir = SensorManager.AXIS_Y; + yDir = SensorManager.AXIS_MINUS_X; + } + 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(); + } + + private Integer getAndroidSensorSpeed(int sensorInputSpeed) { + Integer androidSpeed = null; + switch (sensorInputSpeed) { + case SensorInput.SENSOR_SPEED_SLOW: + androidSpeed = SensorManager.SENSOR_DELAY_UI; + break; + case SensorInput.SENSOR_SPEED_MEDIUM: + androidSpeed = SensorManager.SENSOR_DELAY_NORMAL; + break; + case SensorInput.SENSOR_SPEED_FAST: + androidSpeed = SensorManager.SENSOR_DELAY_GAME; + break; + default: + throw new IllegalArgumentException("Invalid Sensor Speed."); + } + return androidSpeed; + } + + /** + * 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 + */ + private boolean updateOrientation() { + SensorData sensorData; + sensorData = sensors.get((Integer)SensorInput.SENSOR_TYPE_MAGNETIC_FIELD); + if (sensorData == null || !sensorData.enabled) { + return false; + } + sensorData = sensors.get((Integer)SensorInput.SENSOR_TYPE_ACCELEROMETER); + if (sensorData == null || !sensorData.enabled) { + return false; + } + + sensorData = sensors.get((Integer)SensorInput.SENSOR_TYPE_ORIENTATION); + if (sensorData != null && sensorData.enabled) { + + // create new copies so they don't get updated during the getRotationMatrix call + final float[] accValues = new float[3]; + final float[] magValues = new float[3]; + synchronized(curAccValues) { + accValues[0] = curAccValues[0]; + accValues[1] = curAccValues[1]; + accValues[2] = curAccValues[2]; + } + synchronized(curMagValues) { + magValues[0] = curMagValues[0]; + magValues[1] = curMagValues[1]; + magValues[2] = curMagValues[2]; + } + + if (SensorManager.getRotationMatrix(curRotation, curInclination, accValues, magValues)) { + final float [] orientValues = new float[3]; + if (remapCoordinates(curRotation, rotatedRotation)) { + SensorManager.getOrientation(rotatedRotation, orientValues); +// logger.log(Level.INFO, "Orientation Values: {0}, {1}, {2}", +// new Object[]{orientValues[0], orientValues[1], orientValues[2]}); + + updateEventQueue(SensorInput.SENSOR_TYPE_ORIENTATION, + orientValues[0], orientValues[1], orientValues[2], System.nanoTime()); + + return true; + } else { + //logger.log(Level.INFO, "remapCoordinateSystem failed"); + } + + } else { + //logger.log(Level.INFO, "getRotationMatrix returned false"); + } + + } else { + if (!sensorData.enabled) { + //logger.log(Level.INFO, "Orientation is not active"); + } + } + return false; + } + + private void updateEventQueue(int sensorType, float x, float y, float z, long timestamp) { +// logger.log(Level.INFO, "updateEventQueue for {0}: values: {1}, {2}, {3}", +// new Object[]{sensorType, x, y, z}); + float lastX, lastY, lastZ; + float dX, dY, dZ; + + SensorData sensorData = sensors.get((Integer)sensorType); + + if (sensorData != null) { + // if lastValues is null, then this is the first scan after a registerListener + // so set lastValues to the current values so dX,dY,dZ are zero this pass + if (sensorData.lastValues == null) { + sensorData.lastValues = new Vector3f(x, y, z); + } + + lastX = sensorData.lastValues.x; + lastY = sensorData.lastValues.y; + lastZ = sensorData.lastValues.z; + + dX = x - lastX; + dY = y - lastY; + dZ = z - lastZ; + + if (dX != 0 && dY != 0 && dZ != 0) { + MotionSensorEvent motionEvent = new MotionSensorEvent(sensorType, x, y, z, dX, dY, dZ); + motionEvent.setTime(timestamp); + sensorData.lastValues.x = x; + sensorData.lastValues.y = y; + sensorData.lastValues.z = z; + + synchronized (eventQueue){ + eventQueue.add(motionEvent); + } + } else { + //logger.log(Level.INFO, "No change in Sensor Data for: {0}", sensorType); + } + } else { + //logger.log(Level.INFO, "Sensor Data is null for: {0}", sensorType); + } + } + + + + + + + + + + + + + + +// Start of methods from SensorInput + + public boolean isEnabled(int sensorType) { + logger.log(Level.INFO, "Checking isEnabled for type: {0}", sensorType); + SensorData sensorData = sensors.get((Integer)sensorType); + if (sensorData == null) { +// logger.log(Level.INFO, "sensor data is null, sensors size is: {0}", sensors.size()); + return false; + } + return sensors.get((Integer)sensorType).enabled; + } + + public void setEnable(boolean enable) { + for (Integer sensorType: sensors.keySet()) { + setEnable(sensorType, enable); + } + } + + public void setEnable(int sensorType, boolean enable) { + logger.log(Level.INFO, "Setting Sensor {0} Enable to {1}", + new Object[]{sensorType, enable}); + if (enable) { +// registerListener(sensorType, true); + registerListener(sensorType); + } else { + unRegisterListener(sensorType); + } + } + + public void setSensorFrequency(int sensorType, int updateSpeed) { + SensorData sensorData = sensors.get((Integer)sensorType); + if (sensorData == null || sensorData.enabled) { + throw new IllegalArgumentException("Sensor Type Not Configured or is already active."); + } + + sensorData.androidSensorSpeed = getAndroidSensorSpeed(updateSpeed); + } + + public Set getSensorTypes() { + return Collections.unmodifiableSet(sensors.keySet()); + } + + public void setSensorMinChange(int sensorType, float minChangePercent) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void initialize() { + logger.log(Level.INFO, "Doing Initialize."); + initSensorManager(); + initialized = true; + } + + public void update() { + updateOrientation(); + synchronized (eventQueue){ + // flush events to listener + for (int i = 0; i < eventQueue.size(); i++){ + listener.onMotionSensorEvent(eventQueue.get(i)); + } + eventQueue.clear(); + } + } + + public void destroy() { + for (Integer i: sensors.keySet()) { + unRegisterListener(i); + } + logger.log(Level.INFO, "Doing Destroy"); + if (sensorManager != null) { + sensorManager.unregisterListener(this); + } + sensors.clear(); + eventQueue.clear(); + } + + public boolean isInitialized() { + return initialized; + } + + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + public long getInputTimeNanos() { + return System.nanoTime(); + } + +// End of methods from SensorInput + +// Start of methods from SensorEventListener + + public void onSensorChanged(SensorEvent se) { +// logger.log(Level.INFO, "onSensorChanged for {0}: values: {1}, {2}, {3}", +// new Object[]{se.sensor.getType(), se.values[0], se.values[1], se.values[2]}); + SensorData sensorData; + int sensorType; + for (Entry entry : sensors.entrySet()) { +// if (entry.getValue().sensor == null) { +// logger.log(Level.INFO, "Sensor is null for SensorType: {0}", entry.getKey()); +// } + if (entry.getValue().sensor != null && entry.getValue().sensor.equals(se.sensor)) { + sensorType = entry.getKey(); + sensorData = entry.getValue(); + + updateEventQueue(sensorType, se.values[0], se.values[1], se.values[2], se.timestamp); + + if (sensorType == SensorInput.SENSOR_TYPE_MAGNETIC_FIELD) { + synchronized(curMagValues) { + curMagValues[0] = se.values[0]; + curMagValues[1] = se.values[1]; + curMagValues[2] = se.values[2]; + } + } + if (sensorType == SensorInput.SENSOR_TYPE_ACCELEROMETER) { + synchronized(curAccValues) { + curAccValues[0] = se.values[0]; + curAccValues[1] = se.values[1]; + curAccValues[2] = se.values[2]; + } + } + break; + } + } + } + + + public void onAccuracyChanged(Sensor sensor, int i) { + logger.log(Level.INFO, "onAccuracyChanged for {0}: accuracy: {1}", + new Object[]{sensor.toString(), i}); + } + +// End of methods from SensorEventListener + +} diff --git a/engine/src/android/com/jme3/system/android/OGLESContext.java b/engine/src/android/com/jme3/system/android/OGLESContext.java index 8c3df1aef..2a51a5244 100644 --- a/engine/src/android/com/jme3/system/android/OGLESContext.java +++ b/engine/src/android/com/jme3/system/android/OGLESContext.java @@ -13,8 +13,8 @@ * 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 + * * 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 @@ -32,7 +32,6 @@ package com.jme3.system.android; import com.jme3.renderer.android.AndroidGLSurfaceView; -import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.opengl.GLSurfaceView; @@ -44,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.AndroidSensorInput; import com.jme3.input.controls.SoftTextDialogInputListener; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; @@ -75,6 +75,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInput androidInput; + protected AndroidSensorInput androidSensorInput; protected AndroidGLSurfaceView view; protected int minFrameDuration = 0; // No FPS cap /** @@ -92,15 +93,15 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex } /** - * createView creates the GLSurfaceView that the + * createView creates the GLSurfaceView that the * renderer will draw to. *

* The result GLSurfaceView will receive input events and forward * them to the Application. Any rendering will be done into * the GLSurfaceView. Only one GLSurfaceView can be created at this time. * The given configType specifies how to determine the display configuration. - * - * + * + * * @param configType ConfigType.FASTEST (Default) | ConfigType.LEGACY | * ConfigType.BEST * @param eglConfigVerboseLogging if true show all found configs @@ -114,6 +115,13 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex } else { androidInput.setView(view); } + if (androidSensorInput == null) { + logger.log(Level.INFO, "Creating New SensorInput"); + androidSensorInput = new AndroidSensorInput(); + } else { + logger.log(Level.INFO, "Resetting SensorInput"); + androidSensorInput.resetSensorManager(); + } if (configType == ConfigType.LEGACY) { // Hardcoded egl setup clientOpenGLESVersion = 2; @@ -277,6 +285,11 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex return androidInput; } + @Override + public SensorInput getSensorInput() { + return androidSensorInput; + } + @Override public Timer getTimer() { return timer; diff --git a/engine/src/core/com/jme3/app/Application.java b/engine/src/core/com/jme3/app/Application.java index 688c2ef65..75618beab 100644 --- a/engine/src/core/com/jme3/app/Application.java +++ b/engine/src/core/com/jme3/app/Application.java @@ -62,14 +62,14 @@ import java.util.logging.Logger; * * jME3 applications should extend this class and call start() to begin the * application. - * + * */ public class Application implements SystemListener { private static final Logger logger = Logger.getLogger(Application.class.getName()); protected AssetManager assetManager; - + protected AudioRenderer audioRenderer; protected Renderer renderer; protected RenderManager renderManager; @@ -90,6 +90,7 @@ public class Application implements SystemListener { protected KeyInput keyInput; protected JoyInput joyInput; protected TouchInput touchInput; + protected SensorInput sensorInput; protected InputManager inputManager; protected AppStateManager stateManager; @@ -104,10 +105,10 @@ public class Application implements SystemListener { /** * Returns true if pause on lost focus is enabled, false otherwise. - * + * * @return true if pause on lost focus is enabled - * - * @see #setPauseOnLostFocus(boolean) + * + * @see #setPauseOnLostFocus(boolean) */ public boolean isPauseOnLostFocus() { return pauseOnFocus; @@ -117,13 +118,13 @@ public class Application implements SystemListener { * Enable or disable pause on lost focus. *

* By default, pause on lost focus is enabled. - * If enabled, the application will stop updating - * when it loses focus or becomes inactive (e.g. alt-tab). + * If enabled, the application will stop updating + * when it loses focus or becomes inactive (e.g. alt-tab). * For online or real-time applications, this might not be preferable, * so this feature should be set to disabled. For other applications, * it is best to keep it on so that CPU usage is not used when - * not necessary. - * + * not necessary. + * * @param pauseOnLostFocus True to enable pause on lost focus, false * otherwise. */ @@ -199,30 +200,30 @@ public class Application implements SystemListener { */ public void setTimer(Timer timer){ this.timer = timer; - + if (timer != null) { timer.reset(); } - + if (renderManager != null) { renderManager.setTimer(timer); } } - + public Timer getTimer(){ return timer; - } + } private void initDisplay(){ // aquire important objects // from the context settings = context.getSettings(); - - // Only reset the timer if a user has not already provided one + + // Only reset the timer if a user has not already provided one if (timer == null) { timer = context.getTimer(); } - + renderer = context.getRenderer(); } @@ -274,7 +275,7 @@ public class Application implements SystemListener { keyInput = context.getKeyInput(); if (keyInput != null) keyInput.initialize(); - + touchInput = context.getTouchInput(); if (touchInput != null) touchInput.initialize(); @@ -285,12 +286,16 @@ public class Application implements SystemListener { joyInput.initialize(); } - inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); + sensorInput = context.getSensorInput(); + if (sensorInput != null) + sensorInput.initialize(); + + inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput, sensorInput); } private void initStateManager(){ stateManager = new AppStateManager(this); - + // Always register a ResetStatsState to make sure // that the stats are cleared every frame stateManager.attach(new ResetStatsState()); @@ -361,15 +366,15 @@ public class Application implements SystemListener { /** * Starts the application in {@link Type#Display display} mode. - * - * @see #start(com.jme3.system.JmeContext.Type) + * + * @see #start(com.jme3.system.JmeContext.Type) */ public void start(){ start(JmeContext.Type.Display); } /** - * Starts the application. + * Starts the application. * Creating a rendering context and executing * the main loop in a separate thread. */ @@ -382,7 +387,7 @@ public class Application implements SystemListener { if (settings == null){ settings = new AppSettings(true); } - + logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); context = JmeSystem.newContext(settings, contextType); context.setSystemListener(this); @@ -392,15 +397,15 @@ public class Application implements SystemListener { /** * Initializes the application's canvas for use. *

- * After calling this method, cast the {@link #getContext() context} to + * After calling this method, cast the {@link #getContext() context} to * {@link JmeCanvasContext}, * then acquire the canvas with {@link JmeCanvasContext#getCanvas() } * and attach it to an AWT/Swing Frame. * The rendering thread will start when the canvas becomes visible on * screen, however if you wish to start the context immediately you * may call {@link #startCanvas() } to force the rendering thread - * to start. - * + * to start. + * * @see JmeCanvasContext * @see Type#Canvas */ @@ -423,8 +428,8 @@ public class Application implements SystemListener { * Starts the rendering thread after createCanvas() has been called. *

* Same as calling startCanvas(false) - * - * @see #startCanvas(boolean) + * + * @see #startCanvas(boolean) */ public void startCanvas(){ startCanvas(false); @@ -435,8 +440,8 @@ public class Application implements SystemListener { *

* Calling this method is optional, the canvas will start automatically * when it becomes visible. - * - * @param waitFor If true, the current thread will block until the + * + * @param waitFor If true, the current thread will block until the * rendering thread is running */ public void startCanvas(boolean waitFor){ @@ -444,7 +449,7 @@ public class Application implements SystemListener { } /** - * Internal use only. + * Internal use only. */ public void reshape(int w, int h){ renderManager.notifyReshape(w, h); @@ -453,7 +458,7 @@ public class Application implements SystemListener { /** * Restarts the context, applying any changed settings. *

- * Changes to the {@link AppSettings} of this Application are not + * Changes to the {@link AppSettings} of this Application are not * applied immediately; calling this method forces the context * to restart, applying the new settings. */ @@ -465,10 +470,10 @@ public class Application implements SystemListener { /** * Requests the context to close, shutting down the main loop * and making necessary cleanup operations. - * + * * Same as calling stop(false) - * - * @see #stop(boolean) + * + * @see #stop(boolean) */ public void stop(){ stop(false); @@ -476,7 +481,7 @@ public class Application implements SystemListener { /** * Requests the context to close, shutting down the main loop - * and making necessary cleanup operations. + * and making necessary cleanup operations. * After the application has stopped, it cannot be used anymore. */ public void stop(boolean waitFor){ @@ -501,7 +506,7 @@ public class Application implements SystemListener { initDisplay(); initCamera(); - + if (inputEnabled){ initInput(); } @@ -522,12 +527,12 @@ public class Application implements SystemListener { logger.log(Level.SEVERE, errMsg, t); // Display error message on screen if (t != null) { - JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() + + JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() + (t.getMessage() != null ? ": " + t.getMessage() : "")); } else { JmeSystem.showErrorDialog(errMsg); } - + stop(); // stop the application } @@ -563,7 +568,7 @@ public class Application implements SystemListener { /** * Enqueues a task/callable object to execute in the jME3 - * rendering thread. + * rendering thread. *

* Callables are executed right at the beginning of the main loop. * They are executed even if the application is currently paused @@ -585,7 +590,7 @@ public class Application implements SystemListener { task.invoke(); } } - } + } /** * Do not call manually. @@ -594,9 +599,9 @@ public class Application implements SystemListener { public void update(){ // Make sure the audio renderer is available to callables AudioContext.setAudioRenderer(audioRenderer); - + runQueuedTasks(); - + if (speed == 0 || paused) return; @@ -622,9 +627,12 @@ public class Application implements SystemListener { if (joyInput != null) joyInput.destroy(); - + if (touchInput != null) - touchInput.destroy(); + touchInput.destroy(); + + if (sensorInput != null) + sensorInput.destroy(); inputManager = null; } @@ -635,11 +643,11 @@ public class Application implements SystemListener { */ public void destroy(){ stateManager.cleanup(); - + destroyInput(); if (audioRenderer != null) audioRenderer.cleanup(); - + timer.reset(); } diff --git a/engine/src/core/com/jme3/input/InputManager.java b/engine/src/core/com/jme3/input/InputManager.java index 86661fad3..ca2fc3c6a 100644 --- a/engine/src/core/com/jme3/input/InputManager.java +++ b/engine/src/core/com/jme3/input/InputManager.java @@ -88,6 +88,7 @@ public class InputManager implements RawInputListener { private final MouseInput mouse; private final JoyInput joystick; private final TouchInput touch; + private final SensorInput sensor; private float frameTPF; private long lastLastUpdateTime = 0; private long lastUpdateTime = 0; @@ -127,9 +128,10 @@ public class InputManager implements RawInputListener { * @param keys * @param joystick * @param touch + * @param sensor * @throws IllegalArgumentException If either mouseInput or keyInput are null. */ - public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) { + public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch, SensorInput sensor) { if (keys == null || mouse == null) { throw new NullPointerException("Mouse or keyboard cannot be null"); } @@ -138,6 +140,7 @@ public class InputManager implements RawInputListener { this.mouse = mouse; this.joystick = joystick; this.touch = touch; + this.sensor = sensor; keys.setInputListener(this); mouse.setInputListener(this); @@ -148,6 +151,9 @@ public class InputManager implements RawInputListener { if (touch != null) { touch.setInputListener(this); } + if (sensor != null) { + sensor.setInputListener(this); + } firstTime = keys.getInputTimeNanos(); } @@ -442,6 +448,50 @@ public class InputManager implements RawInputListener { inputQueue.add(evt); } + private void onMotionSensorEventQueued(MotionSensorEvent evt) { + int hash = SensorTrigger.sensorHash(evt.getSensorType()); + + ArrayList maps = bindings.get(hash); + if (maps == null) { + return; + } + + int size = maps.size(); + + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + + if (listener instanceof MotionSensorListener) { + ((MotionSensorListener) listener).onMotionSensorChange(mapping.name, evt.getSensorType(), evt.getX(), evt.getY(), evt.getZ(), evt.getDX(), evt.getDY(), evt.getDZ()); + } + + } + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onMotionSensorEvent(MotionSensorEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("SensorInput has raised an event at an illegal time."); + } + inputQueue.add(evt); + } + + /** + * Returns the SensorInput implementation. Use this as an entry point to + * enable and disable various sensors as well as setting other sensor settings. + * @return The SensorInput implementation. + */ + public SensorInput getSensorInput() { + return sensor; + } + /** * Set the deadzone for joystick axes. * @@ -784,6 +834,8 @@ public class InputManager implements RawInputListener { listener.onJoyButtonEvent((JoyButtonEvent) event); } else if (event instanceof TouchEvent) { listener.onTouchEvent((TouchEvent) event); + } else if (event instanceof MotionSensorEvent) { + listener.onMotionSensorEvent((MotionSensorEvent) event); } else { assert false; } @@ -810,6 +862,8 @@ public class InputManager implements RawInputListener { onJoyButtonEventQueued((JoyButtonEvent) event); } else if (event instanceof TouchEvent) { onTouchEventQueued((TouchEvent) event); + } else if (event instanceof MotionSensorEvent) { + onMotionSensorEventQueued((MotionSensorEvent) event); } else { assert false; } @@ -850,6 +904,9 @@ public class InputManager implements RawInputListener { if (touch != null) { touch.update(); } + if (sensor != null) { + sensor.update(); + } eventsPermitted = false; diff --git a/engine/src/core/com/jme3/input/RawInputListener.java b/engine/src/core/com/jme3/input/RawInputListener.java index dfdd69e33..1baf573c2 100644 --- a/engine/src/core/com/jme3/input/RawInputListener.java +++ b/engine/src/core/com/jme3/input/RawInputListener.java @@ -40,15 +40,15 @@ import com.jme3.input.event.*; public interface RawInputListener { /** - * Called before a batch of input will be sent to this - * RawInputListener. + * Called before a batch of input will be sent to this + * RawInputListener. */ public void beginInput(); - + /** * Called after a batch of input was sent to this - * RawInputListener. - * + * RawInputListener. + * * The listener should set the {@link InputEvent#setConsumed() consumed flag} * on any events that have been consumed either at this call or previous calls. */ @@ -56,44 +56,51 @@ public interface RawInputListener { /** * Invoked on joystick axis events. - * - * @param evt + * + * @param evt */ public void onJoyAxisEvent(JoyAxisEvent evt); - + /** * Invoked on joystick button presses. - * - * @param evt + * + * @param evt */ public void onJoyButtonEvent(JoyButtonEvent evt); - + /** * Invoked on mouse movement/motion events. - * - * @param evt + * + * @param evt */ public void onMouseMotionEvent(MouseMotionEvent evt); - + /** * Invoked on mouse button events. - * - * @param evt + * + * @param evt */ public void onMouseButtonEvent(MouseButtonEvent evt); - + /** * Invoked on keyboard key press or release events. - * - * @param evt + * + * @param evt */ public void onKeyEvent(KeyInputEvent evt); - - + + /** * Invoked on touchscreen touch events. - * - * @param evt + * + * @param evt */ public void onTouchEvent(TouchEvent evt); + + /** + * Invoked on motion sensor events. + * + * @param evt + */ + public void onMotionSensorEvent(MotionSensorEvent evt); } diff --git a/engine/src/core/com/jme3/input/SensorInput.java b/engine/src/core/com/jme3/input/SensorInput.java new file mode 100644 index 000000000..a90393c80 --- /dev/null +++ b/engine/src/core/com/jme3/input/SensorInput.java @@ -0,0 +1,146 @@ +/* + * 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; + +import java.util.Set; + +/** + * A specific API for interfacing with sensors. + * + * In order to conserve battery power for handheld devices, sensors must be + * enabled before data will be sent. Use the setEnable method to enable or disable + * the sensors. + * + * Sensor speed can also be set. Constants in this class are used so that each + * platform implementation can decide what absolute update rate to use. Use the + * setSensorFrequency method to set the desired update rate. + * + * In order to minimize the amount of data sent to the application, there is a + * method available to set how much change is required between sensor readings + * before the new updated data is sent. Use the setSensorMinChange method to set + * a minimum data change percentage (percentage of max sensor range). Data will + * not be sent to the application until the data has changed by this amount. + * + * + * + * + * + * @author iwgeric + */ +public interface SensorInput extends Input { + + /** + * Orientation Sensor. Values returned in the onMotionSensorChanged event + * are in radians. + */ + public static final int SENSOR_TYPE_ORIENTATION = 0; + + /** + * Accelerometer Sensor. Values returned in the onMotionSensorChanged event + * are in m/s^2. Values include gravity. To get true device acceleration, + * gravity must be removed. + */ + public static final int SENSOR_TYPE_ACCELEROMETER = 1; + + /** + * Magnetic Field Sensor. Values returned in the onMotionSensorChanged event + * are in micro-Tesla (uT). + */ + public static final int SENSOR_TYPE_MAGNETIC_FIELD = 2; + + /** + * Slowest Sensor Update Speed + */ + public static final int SENSOR_SPEED_SLOW = 0; + + /** + * Medium Sensor Update Speed + */ + public static final int SENSOR_SPEED_MEDIUM = 1; + + /** + * Fastest Sensor Update Speed + */ + public static final int SENSOR_SPEED_FAST = 2; + + /** + * Returns whether a sensor is enabled or not. + * + * @param sensorType The sensor type. + * @return whether a sensor is enabled or not. + */ + public boolean isEnabled(int sensorType); + + /** + * Sets enable/disable for a specific sensor type. + * + * @param sensorType The sensor type. + * @param enable True to enable, False to disable. + */ + public void setEnable(int sensorType, boolean enable); + + /** + * Sets enable/disable for all sensor types. + * + * @param enable True to enable, False to disable. + */ + public void setEnable(boolean enable); + + /** + * Returns a list of available sensor types. + * + * @return a list of available sensor types. + */ + public Set getSensorTypes(); + + /** + * Set the minimum amount of change that is required before an event + * is created for the sensor. minChangePercent is defined as a percentage + * of the maximum sensor range. + * + * @param sensorType The sensor type. + * @param minChangePercent Percentage of changed required before creating an event. + */ + public void setSensorMinChange(int sensorType, float minChangePercent); + + /** + * Set the update frequency for the sensor. Use the defined constants in + * SensorInput for setting the speed becuase the actual update frequency is + * platform dependant. + * + * @param sensorType The sensor type. + * @param updateSpeed Target update speed as a constant (do not use absolute values) + */ + public void setSensorFrequency(int sensorType, int updateSpeed); + +} diff --git a/engine/src/core/com/jme3/input/controls/MotionSensorListener.java b/engine/src/core/com/jme3/input/controls/MotionSensorListener.java new file mode 100644 index 000000000..4ef4d23f9 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/MotionSensorListener.java @@ -0,0 +1,58 @@ +/* + * 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.controls; + +import com.jme3.input.SensorInput; + +/** + * MotionSensorListener is used to receive events from Sensors + * + * @author iwgeric + */ +public interface MotionSensorListener extends InputListener { + + /** + * Called when data from a sensor has been updated. + * + * @param name The name of the mapping that was invoked + * @param sensorType Sensor Type value from {@link SensorInput}. + * @param x X component of the new sensor data based on the sensor type. + * @param y Y component of the new sensor data based on the sensor type. + * @param z Z component of the new sensor data based on the sensor type. + * @param dX Change in the x component from the last update. + * @param dY Change in the y component from the last update. + * @param dZ Change in the z component from the last update. + */ + public void onMotionSensorChange(String name, int sensorType, float x, float y, float z, float dX, float dY, float dZ); + +} diff --git a/engine/src/core/com/jme3/input/controls/SensorTrigger.java b/engine/src/core/com/jme3/input/controls/SensorTrigger.java new file mode 100644 index 000000000..3fc7f6aa4 --- /dev/null +++ b/engine/src/core/com/jme3/input/controls/SensorTrigger.java @@ -0,0 +1,83 @@ +/* + * 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.controls; + +import com.jme3.input.SensorInput; + +/** + * A SensorTrigger is used as a mapping to receive events + * from a sensor. + * + * @author Kirill Vainer + */ +public class SensorTrigger implements Trigger { + +// private final SensorInput.Type sensorType; + private final int sensorType; + + /** + * Create a new SensorTrigger to receive sensor events. + * + * @param Sensor Type. See {@link SensorInput}. + */ +// public SensorTrigger(SensorInput.Type sensorType) { + public SensorTrigger(int sensorType) { +// if (sensorType == null) + if (sensorType < 0 || sensorType > 255) + throw new IllegalArgumentException("Invalide Sensor Type"); + + this.sensorType = sensorType; + } + +// public SensorInput.Type getSensorType() { + public int getSensorType() { + return sensorType; + } + + public String getName() { + return sensorType + " Sensor"; + } + +// public static int sensorHash(SensorInput.Type sensorType){ + public static int sensorHash(int sensorType){ +// assert sensorType != null; +// return 256 | (sensorType.ordinal() & 0xff); + assert sensorType >= 0 && sensorType <= 255; + return 1024 | (sensorType & 0xff); + } + + public int triggerHashCode() { + return sensorHash(sensorType); + } + +} diff --git a/engine/src/core/com/jme3/input/event/MotionSensorEvent.java b/engine/src/core/com/jme3/input/event/MotionSensorEvent.java new file mode 100644 index 000000000..ada7c7d95 --- /dev/null +++ b/engine/src/core/com/jme3/input/event/MotionSensorEvent.java @@ -0,0 +1,118 @@ +/* + * 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.event; + +/** + * Motion Sensor event. + * + * @author iwgeric + */ +public class MotionSensorEvent extends InputEvent { + + private int sensorType; + private float x, y, z, dX, dY, dZ; + + public MotionSensorEvent(int sensorType, float x, float y, float z, float dX, float dY, float dZ) { + this.sensorType = sensorType; + this.x = x; + this.y = y; + this.z = z; + this.dX = dX; + this.dY = dY; + this.dZ = dZ; + } + + /** + * Sensor Type + * @return Sensor Type + */ + public int getSensorType() { + return sensorType; + } + + /** + * Current X coordinate + * @return Current X coordinate + */ + public float getX() { + return x; + } + + /** + * Current Y coordinate + * @return Current Y coordinate + */ + public float getY() { + return y; + } + + /** + * Current Z coordinate + * @return Current Z coordinate + */ + public float getZ() { + return z; + } + + /** + * The change in X coordinate + * @return change in X coordinate + */ + public float getDX() { + return dX; + } + + /** + * The change in Y coordinate + * + * @return change in Y coordinate + */ + public float getDY() { + return dY; + } + + /** + * The change in Z coordinate + * + * @return change in Z coordinate + */ + public float getDZ() { + return dZ; + } + + @Override + public String toString(){ + return "MotionSensor(Type="+sensorType+", X="+x+", Y="+y+", Z="+z+", DX="+dX+", DY="+dY+", DZ="+dZ+")"; + } + +} diff --git a/engine/src/core/com/jme3/system/JmeContext.java b/engine/src/core/com/jme3/system/JmeContext.java index 37283a503..dea7053d8 100644 --- a/engine/src/core/com/jme3/system/JmeContext.java +++ b/engine/src/core/com/jme3/system/JmeContext.java @@ -35,6 +35,7 @@ package com.jme3.system; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.SensorInput; import com.jme3.input.TouchInput; import com.jme3.renderer.Renderer; @@ -56,7 +57,7 @@ public interface JmeContext { * display with the windowing system. */ Display, - + /** * A canvas type context makes a rendering surface available as an * AWT {@link java.awt.Canvas} object that can be embedded in a Swing/AWT @@ -64,7 +65,7 @@ public interface JmeContext { * to {@link JmeCanvasContext}. */ Canvas, - + /** * An OffscreenSurface is a context that is not visible * by the user. The application can use the offscreen surface to do @@ -85,7 +86,7 @@ public interface JmeContext { * @return The type of the context. */ public Type getType(); - + /** * @param settings the display settings to use for the created context. If * the context has already been created, then restart() must be called @@ -100,7 +101,7 @@ public interface JmeContext { public void setSystemListener(SystemListener listener); /** - * @return The current display settings. Note that they might be + * @return The current display settings. Note that they might be * different from the ones set with setDisplaySettings() if the context * was restarted or the settings changed internally. */ @@ -125,17 +126,22 @@ public interface JmeContext { * @return Joystick input implementation. May be null if not available. */ public JoyInput getJoyInput(); - + /** * @return Touch device input implementation. May be null if not available. */ public TouchInput getTouchInput(); - + + /** + * @return Sensor device input implementation. May be null if not available. + */ + public SensorInput getSensorInput(); + /** * @return The timer for this context, or null if not created yet. */ public Timer getTimer(); - + /** * Sets the title of the display (if available). This does nothing * for fullscreen, headless, or canvas contexts. diff --git a/engine/src/core/com/jme3/system/NullContext.java b/engine/src/core/com/jme3/system/NullContext.java index 75fdf2b5b..c15e39815 100644 --- a/engine/src/core/com/jme3/system/NullContext.java +++ b/engine/src/core/com/jme3/system/NullContext.java @@ -35,6 +35,7 @@ package com.jme3.system; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.SensorInput; import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; @@ -135,7 +136,7 @@ public class NullContext implements JmeContext, Runnable { } deinitInThread(); - + logger.info("NullContext destroyed."); } @@ -173,11 +174,15 @@ public class NullContext implements JmeContext, Runnable { public JoyInput getJoyInput() { return null; } - + public TouchInput getTouchInput() { return null; } + public SensorInput getSensorInput() { + return null; + } + public void setTitle(String title) { } @@ -225,6 +230,6 @@ public class NullContext implements JmeContext, Runnable { public boolean isRenderable() { return true; // Doesn't really matter if true or false. Either way - // RenderManager won't render anything. + // RenderManager won't render anything. } } diff --git a/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java b/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java index c980c4022..6891aba25 100644 --- a/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java +++ b/engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java @@ -3,6 +3,7 @@ package com.jme3.system.awt; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.SensorInput; import com.jme3.input.TouchInput; import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; @@ -17,12 +18,12 @@ public class AwtPanelsContext implements JmeContext { protected SystemListener listener; protected ArrayList panels = new ArrayList(); protected AwtPanel inputSource; - + protected AwtMouseInput mouseInput = new AwtMouseInput(); protected AwtKeyInput keyInput = new AwtKeyInput(); - + protected boolean lastThrottleState = false; - + private class AwtPanelsListener implements SystemListener { public void initialize() { @@ -60,20 +61,20 @@ public class AwtPanelsContext implements JmeContext { destroyInThread(); } } - + public void setInputSource(AwtPanel panel){ if (!panels.contains(panel)) throw new IllegalArgumentException(); - + inputSource = panel; mouseInput.setInputSource(panel); keyInput.setInputSource(panel); } - + public Type getType() { return Type.OffscreenSurface; } - + public void setSystemListener(SystemListener listener) { this.listener = listener; } @@ -102,6 +103,10 @@ public class AwtPanelsContext implements JmeContext { return null; } + public SensorInput getSensorInput() { + return null; + } + public Timer getTimer() { return actualContext.getTimer(); } @@ -113,31 +118,31 @@ public class AwtPanelsContext implements JmeContext { public boolean isRenderable() { return actualContext != null && actualContext.isRenderable(); } - + public AwtPanelsContext(){ } - + public AwtPanel createPanel(PaintMode paintMode){ AwtPanel panel = new AwtPanel(paintMode); panels.add(panel); return panel; } - + private void initInThread(){ listener.initialize(); } - + private void updateInThread(){ // Check if throttle required boolean needThrottle = true; - + for (AwtPanel panel : panels){ if (panel.isActiveDrawing()){ needThrottle = false; break; } } - + if (lastThrottleState != needThrottle){ lastThrottleState = needThrottle; if (lastThrottleState){ @@ -146,21 +151,21 @@ public class AwtPanelsContext implements JmeContext { System.out.println("OGL: Ceased throttling update loop."); } } - + if (needThrottle){ try { Thread.sleep(100); } catch (InterruptedException ex) { } } - + listener.update(); } - + private void destroyInThread(){ listener.destroy(); } - + public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); this.settings.setRenderer(AppSettings.LWJGL_OPENGL2); @@ -173,7 +178,7 @@ public class AwtPanelsContext implements JmeContext { if (actualContext != null){ throw new IllegalStateException("Already created"); } - + actualContext = JmeSystem.newContext(settings, Type.OffscreenSurface); actualContext.setSystemListener(new AwtPanelsListener()); actualContext.create(waitFor); @@ -182,15 +187,15 @@ public class AwtPanelsContext implements JmeContext { public void destroy(boolean waitFor) { if (actualContext == null) throw new IllegalStateException("Not created"); - + // destroy parent context actualContext.destroy(waitFor); } - + public void setTitle(String title) { // not relevant, ignore } - + public void setAutoFlushFrames(boolean enabled) { // not relevant, ignore } @@ -198,5 +203,5 @@ public class AwtPanelsContext implements JmeContext { public void restart() { // only relevant if changing pixel format. } - + } diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index c40019e3f..b9096bda6 100644 --- a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -35,6 +35,7 @@ package com.jme3.system.lwjgl; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.SensorInput; import com.jme3.input.TouchInput; import com.jme3.input.lwjgl.JInputJoyInput; import com.jme3.input.lwjgl.LwjglKeyInput; @@ -54,7 +55,7 @@ import org.lwjgl.opengl.Util; public abstract class LwjglAbstractDisplay extends LwjglContext implements Runnable { private static final Logger logger = Logger.getLogger(LwjglAbstractDisplay.class.getName()); - + protected AtomicBoolean needClose = new AtomicBoolean(false); protected boolean wasActive = false; protected int frameRate = 0; @@ -98,7 +99,7 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna public void uncaughtException(Thread thread, Throwable thrown) { listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); if (needClose.get()){ - // listener.handleError() has requested the + // listener.handleError() has requested the // context to close. Satisfy request. deinitInThread(); } @@ -126,7 +127,7 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna listener.handleError("Failed to create display", ex); return false; // if we failed to create display, do not continue } - + listener.initialize(); return true; } @@ -149,7 +150,7 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna throw new IllegalStateException(); listener.update(); - + // All this does is call swap buffers // If the canvas is not active, there's no need to waste time // doing that .. @@ -253,11 +254,15 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna } return keyInput; } - + public TouchInput getTouchInput() { return null; } - + + public SensorInput getSensorInput() { + return null; + } + public void setAutoFlushFrames(boolean enabled){ this.autoFlush = enabled; } diff --git a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index d7424b546..8a761150c 100644 --- a/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/engine/src/lwjgl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -35,6 +35,7 @@ package com.jme3.system.lwjgl; import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; +import com.jme3.input.SensorInput; import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; @@ -53,7 +54,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { private int width; private int height; private PixelFormat pixelFormat; - + protected void initInThread(){ if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0){ logger.severe("Offscreen surfaces are not supported."); @@ -69,7 +70,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { settings.getDepthBits(), settings.getStencilBits(), settings.getSamples()); - + width = settings.getWidth(); height = settings.getHeight(); try{ @@ -122,9 +123,9 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { listener.update(); assert checkGLError(); - + renderer.onFrame(); - + int frameRate = settings.getFrameRate(); if (frameRate >= 1){ Display.sync(frameRate); @@ -133,7 +134,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { protected void deinitInThread(){ renderable.set(false); - + listener.destroy(); renderer.cleanup(); pbuffer.destroy(); @@ -187,11 +188,15 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { public JoyInput getJoyInput() { return null; } - + public TouchInput getTouchInput() { return null; } + public SensorInput getSensorInput() { + return null; + } + public void setTitle(String title) { } diff --git a/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java index dd2de9836..d9efddd92 100644 --- a/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java +++ b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java @@ -216,6 +216,9 @@ public class InputSystemJme implements InputSystem, RawInputListener { public void onJoyButtonEvent(JoyButtonEvent evt) { } + public void onMotionSensorEvent(MotionSensorEvent evt) { + } + public void onKeyEvent(KeyInputEvent evt) { inputQueue.add(evt); } diff --git a/engine/src/test/jme3test/gui/TestBitmapFont.java b/engine/src/test/jme3test/gui/TestBitmapFont.java index 088046e47..eb6a7cae0 100644 --- a/engine/src/test/jme3test/gui/TestBitmapFont.java +++ b/engine/src/test/jme3test/gui/TestBitmapFont.java @@ -47,7 +47,7 @@ public class TestBitmapFont extends SimpleApplication { private String txtB = "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567 890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?"; - + private BitmapText txt; private BitmapText txt2; private BitmapText txt3; @@ -62,7 +62,7 @@ public class TestBitmapFont extends SimpleApplication { inputManager.addMapping("WordWrap", new KeyTrigger(KeyInput.KEY_TAB)); inputManager.addListener(keyListener, "WordWrap"); inputManager.addRawInputListener(textListener); - + BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); txt = new BitmapText(fnt, false); txt.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); @@ -76,33 +76,33 @@ public class TestBitmapFont extends SimpleApplication { txt2.setText("Text without restriction. \nText without restriction. Text without restriction. Text without restriction"); txt2.setLocalTranslation(0, txt2.getHeight(), 0); guiNode.attachChild(txt2); - + txt3 = new BitmapText(fnt, false); txt3.setBox(new Rectangle(0, 0, settings.getWidth(), 0)); txt3.setText("Press Tab to toggle word-wrap. type text and enter to input text"); txt3.setLocalTranslation(0, settings.getHeight()/2, 0); guiNode.attachChild(txt3); } - + private ActionListener keyListener = new ActionListener() { @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("WordWrap") && !isPressed) { txt.setLineWrapMode( txt.getLineWrapMode() == LineWrapMode.Word ? LineWrapMode.NoWrap : LineWrapMode.Word ); - } + } } }; - + private RawInputListener textListener = new RawInputListener() { private StringBuilder str = new StringBuilder(); - + @Override - public void onMouseMotionEvent(MouseMotionEvent evt) { } - + public void onMouseMotionEvent(MouseMotionEvent evt) { } + @Override - public void onMouseButtonEvent(MouseButtonEvent evt) { } - + public void onMouseButtonEvent(MouseButtonEvent evt) { } + @Override public void onKeyEvent(KeyInputEvent evt) { if (evt.isReleased()) @@ -114,21 +114,24 @@ public class TestBitmapFont extends SimpleApplication { str.append(evt.getKeyChar()); } } - + @Override public void onJoyButtonEvent(JoyButtonEvent evt) { } - + @Override public void onJoyAxisEvent(JoyAxisEvent evt) { } - + @Override public void endInput() { } - + @Override public void beginInput() { } @Override public void onTouchEvent(TouchEvent evt) { } + + @Override + public void onMotionSensorEvent(MotionSensorEvent evt) { } }; } diff --git a/engine/src/test/jme3test/gui/TestSoftwareMouse.java b/engine/src/test/jme3test/gui/TestSoftwareMouse.java index 32c680153..4fc59d149 100644 --- a/engine/src/test/jme3test/gui/TestSoftwareMouse.java +++ b/engine/src/test/jme3test/gui/TestSoftwareMouse.java @@ -73,7 +73,9 @@ public class TestSoftwareMouse extends SimpleApplication { } public void onKeyEvent(KeyInputEvent evt) { } - public void onTouchEvent(TouchEvent evt) { + public void onTouchEvent(TouchEvent evt) { + } + public void onMotionSensorEvent(MotionSensorEvent evt) { } }; @@ -93,7 +95,7 @@ public class TestSoftwareMouse extends SimpleApplication { // inputManager.setCursorVisible(false); Texture tex = assetManager.loadTexture("Interface/Logo/Cursor.png"); - + cursor = new Picture("cursor"); cursor.setTexture(assetManager, (Texture2D) tex, true); cursor.setWidth(64);