From cbedf5ef083c5b6e407c25e9d3c47f4029959732 Mon Sep 17 00:00:00 2001 From: iwgeric Date: Tue, 3 May 2016 22:02:31 -0400 Subject: [PATCH] Add test case for using Android Sensors as Joystick Axes (including rumble). --- .../src/main/AndroidManifest.xml | 1 + .../jme3test/android/TestAndroidSensors.java | 314 ++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java diff --git a/jme3-android-examples/src/main/AndroidManifest.xml b/jme3-android-examples/src/main/AndroidManifest.xml index d41aba8ce..cf9cc7c2d 100644 --- a/jme3-android-examples/src/main/AndroidManifest.xml +++ b/jme3-android-examples/src/main/AndroidManifest.xml @@ -45,5 +45,6 @@ + diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java new file mode 100644 index 000000000..3805435d9 --- /dev/null +++ b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java @@ -0,0 +1,314 @@ +package jme3test.android; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickAxis; +import com.jme3.input.MouseInput; +import com.jme3.input.SensorJoystickAxis; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Line; +import com.jme3.texture.Texture; +import com.jme3.util.IntMap; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Example Test Case to test using Android sensors as Joystick axes. Make sure to enable Joystick Events from + * the test chooser menus. Rotating the device will cause the block to rotate. Tapping the screen will cause the + * sensors to be calibrated (reset to zero) at the current orientation. Continuously tapping the screen causes + * the "rumble" to intensify until it reaches the maximum amount and then it shuts off. + * + * @author iwgeric + */ +public class TestAndroidSensors extends SimpleApplication implements ActionListener, AnalogListener { + + private static final Logger logger = Logger.getLogger(TestAndroidSensors.class.getName()); + + private Geometry geomZero = null; + // Map of joysticks saved with the joyId as the key + private IntMap joystickMap = new IntMap(); + // flag to allow for the joystick axis to be calibrated on startup + private boolean initialCalibrationComplete = false; + // mappings used for onAnalog + private final String ORIENTATION_X_PLUS = "Orientation_X_Plus"; + private final String ORIENTATION_X_MINUS = "Orientation_X_Minus"; + private final String ORIENTATION_Y_PLUS = "Orientation_Y_Plus"; + private final String ORIENTATION_Y_MINUS = "Orientation_Y_Minus"; + private final String ORIENTATION_Z_PLUS = "Orientation_Z_Plus"; + private final String ORIENTATION_Z_MINUS = "Orientation_Z_Minus"; + + + // variables to save the current rotation + // Used when controlling the geometry with device orientation + private float[] anglesCurrent = new float[]{0f, 0f, 0f}; + private Quaternion rotationQuat = new Quaternion(); + + // switch to apply an absolute rotation (geometry.setLocalRotation) or + // an incremental constant rotation (geometry.rotate) + // Used when controlling the geometry with device orientation + private boolean useAbsolute = false; + + // rotation speed to use when apply constant incremental rotation + // Used when controlling the geometry with device orientation + private float rotationSpeedX = 1f; + private float rotationSpeedY = 1f; + + // current intensity of the rumble + float rumbleAmount = 0f; + + // toggle to enable rumble + boolean enableRumble = true; + + // toggle to enable device orientation in FlyByCamera + boolean enableFlyByCameraRotation = false; + + // toggle to enable controlling geometry rotation + boolean enableGeometryRotation = true; + + // Make sure to set joystickEventsEnabled = true in MainActivity for Android + + private float toDegrees(float rad) { + return rad * FastMath.RAD_TO_DEG; + } + + @Override + public void simpleInitApp() { + + // useAbsolute = true; + // enableRumble = true; + + if (enableFlyByCameraRotation) { + flyCam.setEnabled(true); + } else { + flyCam.setEnabled(false); + } + + Mesh lineX = new Line(Vector3f.ZERO, Vector3f.ZERO.add(Vector3f.UNIT_X.mult(3))); + Mesh lineY = new Line(Vector3f.ZERO, Vector3f.ZERO.add(Vector3f.UNIT_Y.mult(3))); + Mesh lineZ = new Line(Vector3f.ZERO, Vector3f.ZERO.add(Vector3f.UNIT_Z.mult(3))); + + Geometry geoX = new Geometry("X", lineX); + Material matX = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matX.setColor("Color", ColorRGBA.Red); + matX.getAdditionalRenderState().setLineWidth(30); + geoX.setMaterial(matX); + rootNode.attachChild(geoX); + + Geometry geoY = new Geometry("Y", lineY); + Material matY = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matY.setColor("Color", ColorRGBA.Green); + matY.getAdditionalRenderState().setLineWidth(30); + geoY.setMaterial(matY); + rootNode.attachChild(geoY); + + Geometry geoZ = new Geometry("Z", lineZ); + Material matZ = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matZ.setColor("Color", ColorRGBA.Blue); + matZ.getAdditionalRenderState().setLineWidth(30); + geoZ.setMaterial(matZ); + rootNode.attachChild(geoZ); + + Box b = new Box(1, 1, 1); + geomZero = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Yellow); + Texture tex_ml = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); + mat.setTexture("ColorMap", tex_ml); + geomZero.setMaterial(mat); + geomZero.setLocalTranslation(Vector3f.ZERO); + geomZero.setLocalRotation(Quaternion.IDENTITY); + rootNode.attachChild(geomZero); + + + // Touch (aka MouseInput.BUTTON_LEFT) is used to record the starting + // orientation when using absolute rotations + inputManager.addMapping("MouseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "MouseClick"); + + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks == null || joysticks.length < 1) { + logger.log(Level.INFO, "Cannot find any joysticks!"); + } else { + + // Joysticks return a value of 0 to 1 based on how far the stick is + // push on the axis. This value is then scaled based on how long + // during the frame the joystick axis has been in that position. + // If the joystick is push all the way for the whole frame, + // then the value in onAnalog is equal to tpf. + // If the joystick is push 1/2 way for the entire frame, then the + // onAnalog value is 1/2 tpf. + // Similarly, if the joystick is pushed to the maximum during a frame + // the value in onAnalog will also be scaled. + // For Android sensors, rotating the device 90deg is the same as + // pushing an actual joystick axis to the maximum. + + logger.log(Level.INFO, "Number of joysticks: {0}", joysticks.length); + JoystickAxis axis; + + for (Joystick joystick : joysticks) { + // Get and display all axes in joystick. + List axes = joystick.getAxes(); + for (JoystickAxis joystickAxis : axes) { + logger.log(Level.INFO, "{0} axis scan Name: {1}, LogicalId: {2}, AxisId: {3}", + new Object[]{joystick.getName(), joystickAxis.getName(), joystickAxis.getLogicalId(), joystickAxis.getAxisId()}); + } + + // Get specific axis based on LogicalId of the JoystickAxis + // If found, map axis + axis = joystick.getAxis(SensorJoystickAxis.ORIENTATION_X); + if (axis != null) { + axis.assignAxis(ORIENTATION_X_PLUS, ORIENTATION_X_MINUS); + inputManager.addListener(this, ORIENTATION_X_PLUS, ORIENTATION_X_MINUS); + logger.log(Level.INFO, "Found {0} Joystick, assigning mapping for X axis: {1}, with max value: {2}", + new Object[]{joystick.toString(), axis.toString(), ((SensorJoystickAxis) axis).getMaxRawValue()}); + } + + axis = joystick.getAxis(SensorJoystickAxis.ORIENTATION_Y); + if (axis != null) { + axis.assignAxis(ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS); + inputManager.addListener(this, ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS); + logger.log(Level.INFO, "Found {0} Joystick, assigning mapping for Y axis: {1}, with max value: {2}", + new Object[]{joystick.toString(), axis.toString(), ((SensorJoystickAxis) axis).getMaxRawValue()}); + } + + axis = joystick.getAxis(SensorJoystickAxis.ORIENTATION_Z); + if (axis != null) { + axis.assignAxis(ORIENTATION_Z_PLUS, ORIENTATION_Z_MINUS); + inputManager.addListener(this, ORIENTATION_Z_PLUS, ORIENTATION_Z_MINUS); + logger.log(Level.INFO, "Found {0} Joystick, assigning mapping for Z axis: {1}, with max value: {2}", + new Object[]{joystick.toString(), axis.toString(), ((SensorJoystickAxis) axis).getMaxRawValue()}); + } + + joystickMap.put(joystick.getJoyId(), joystick); + + } + } + } + + + @Override + public void simpleUpdate(float tpf) { + if (!initialCalibrationComplete) { + // Calibrate the axis (set new zero position) if the axis + // is a sensor joystick axis + for (IntMap.Entry entry : joystickMap) { + for (JoystickAxis axis : entry.getValue().getAxes()) { + if (axis instanceof SensorJoystickAxis) { + logger.log(Level.INFO, "Calibrating Axis: {0}", axis.toString()); + ((SensorJoystickAxis) axis).calibrateCenter(); + } + } + } + initialCalibrationComplete = true; + } + + if (enableGeometryRotation) { + rotationQuat.fromAngles(anglesCurrent); + rotationQuat.normalizeLocal(); + + if (useAbsolute) { + geomZero.setLocalRotation(rotationQuat); + } else { + geomZero.rotate(rotationQuat); + } + + anglesCurrent[0] = anglesCurrent[1] = anglesCurrent[2] = 0f; + } + } + + + public void onAction(String string, boolean pressed, float tpf) { + if (string.equalsIgnoreCase("MouseClick") && pressed) { + // Calibrate the axis (set new zero position) if the axis + // is a sensor joystick axis + for (IntMap.Entry entry : joystickMap) { + for (JoystickAxis axis : entry.getValue().getAxes()) { + if (axis instanceof SensorJoystickAxis) { + logger.log(Level.INFO, "Calibrating Axis: {0}", axis.toString()); + ((SensorJoystickAxis) axis).calibrateCenter(); + } + } + } + + if (enableRumble) { + // manipulate joystick rumble + for (IntMap.Entry entry : joystickMap) { + rumbleAmount += 0.1f; + if (rumbleAmount > 1f + FastMath.ZERO_TOLERANCE) { + rumbleAmount = 0f; + } + logger.log(Level.INFO, "rumbling with amount: {0}", rumbleAmount); + entry.getValue().rumble(rumbleAmount); + } + } + } + } + + + public void onAnalog(String string, float value, float tpf) { + logger.log(Level.INFO, "onAnalog for {0}, value: {1}, tpf: {2}", + new Object[]{string, value, tpf}); + float scaledValue = value; + + if (string.equalsIgnoreCase(ORIENTATION_X_PLUS)) { + if (useAbsolute) { + // set rotation amount + // divide by tpf to get back to actual axis value (0 to 1) + // multiply by 90deg so that 90deg = full axis (value = tpf) + anglesCurrent[0] = (scaledValue / tpf * FastMath.HALF_PI); + } else { + // apply an incremental rotation amount based on rotationSpeed + anglesCurrent[0] += scaledValue * rotationSpeedX; + } + } + + if (string.equalsIgnoreCase(ORIENTATION_X_MINUS)) { + if (useAbsolute) { + // set rotation amount + // divide by tpf to get back to actual axis value (0 to 1) + // multiply by 90deg so that 90deg = full axis (value = tpf) + anglesCurrent[0] = (-scaledValue / tpf * FastMath.HALF_PI); + } else { + // apply an incremental rotation amount based on rotationSpeed + anglesCurrent[0] -= scaledValue * rotationSpeedX; + } + } + + if (string.equalsIgnoreCase(ORIENTATION_Y_PLUS)) { + if (useAbsolute) { + // set rotation amount + // divide by tpf to get back to actual axis value (0 to 1) + // multiply by 90deg so that 90deg = full axis (value = tpf) + anglesCurrent[1] = (scaledValue / tpf * FastMath.HALF_PI); + } else { + // apply an incremental rotation amount based on rotationSpeed + anglesCurrent[1] += scaledValue * rotationSpeedY; + } + } + + if (string.equalsIgnoreCase(ORIENTATION_Y_MINUS)) { + if (useAbsolute) { + // set rotation amount + // divide by tpf to get back to actual axis value (0 to 1) + // multiply by 90deg so that 90deg = full axis (value = tpf) + anglesCurrent[1] = (-scaledValue / tpf * FastMath.HALF_PI); + } else { + // apply an incremental rotation amount based on rotationSpeed + anglesCurrent[1] -= scaledValue * rotationSpeedY; + } + } + + } +} \ No newline at end of file