Creating Engine Support for Android Sensor Input

@Momoko_Fan said:
The general problem seems to be that sensor and touch input is too different from keyboard or mouse input so frankly I am not sure about how to implement it within the current system.


Well, for my 2 cents for the sensor input...

1) I think access to the sensors belongs in InputManager because they are indeed inputs to the system.
2) Having a RawInputListener listener (or a seperate one for each sensor type) for the "raw" sensor data also makes sense for people who want to do things with the data everytime it changes (ie. detecting shake based on acceleration data to do something).
3) Providing a method in InputManager to get the SensorInput implementation would allow SensorInput to manage the sensors, store the current sensor values, and not clutter up InputManager with lots of different sensor methods / data. This would be used in application's simpleUpdate to obtain the current (or last data set by the sensor) to control geometry rotation or vehicle / character control based on device orientation.
[java]
Quaterion orientation = inputManager.getSensorInput().getCurrentOrientation();
[/java]
or maybe
[java]
Quaterion orientation = inputManager.getOrientationSensor().getCurrentOrientation();
[/java]
4) If Mappings/Triggers are supposed to be for mouse and keyboard events, then the sensor input shouldn't be made available via Mappings.
1 Like

@wezrule @nehon

After some discussion with Momoko_Fan, the new current theory is to treat the Android sensors as simulated joysticks. Meaning that when the device is rotated, the InputManager will be notified that a joystick axis has changed and provide the app with the axis data via a Joystick Axis mapping.



I am about to remove most of the work that I did previously that made the sensors thier own mapping. The general philosophy is that this does not fit with the design of what mappings were for, so each axis of the orientation will be a seperate joystick axis mapping. Momoko_Fan’s idea is that the user can map an “up” function to be triggered off an Up Arrow, Mouse Forward Motion, and a Joystick Axis so that the existing onAnalog listener could be used. This way Up Arrow or Mouse Forward or Joystick Axis would be used on Desktop and for Android the device rotation would trigger the onAnalog via the Joystick trigger.



Since Android now combines the Magnetic sensor and Accelerometer sensor to calculate the device orientation, a new AndroidJoyInput class would combine the feedback from these 2 sensors to create the device orientation and then notify InputManager of the delta orientation. Each of the 3 axes of orientation would then notify the app via an onAnalog for each axis (same way as Mouse Motion and Joystick Motion work).



@Momoko_Fan

I’m still a little fuzzy on the Raw Listener. I think the general idea is to create a simulated Joystick for orientation, a second simulated Joystick for direction, and a third simulated Joystick for Acceleration. Is that right? Or am I to create a new Raw Listener for sensor feedback that has a callback for each of the 3 different sensor types? It sounds like this direction is to try to fit the sensor data on Android into the Joystick architecture instead of making Sensors available for each platform to implement. If that is correct, then I’m all set to start. If I’ve got something misunderstood, please let me know.

1 Like
@iwgeric said:
I'm still a little fuzzy on the Raw Listener. I think the general idea is to create a simulated Joystick for orientation, a second simulated Joystick for direction, and a third simulated Joystick for Acceleration. Is that right? Or am I to create a new Raw Listener for sensor feedback that has a callback for each of the 3 different sensor types? It sounds like this direction is to try to fit the sensor data on Android into the Joystick architecture instead of making Sensors available for each platform to implement. If that is correct, then I'm all set to start. If I've got something misunderstood, please let me know.

The RawInputListener does not need to change from how it was before we had sensors. It can accept joystick input and handle it as joystick, including sensors. We could have a single joystick for all sensors or we can have a joystick per sensor. I think the latter way is better ..
1 Like
@Momoko_Fan said:
The RawInputListener does not need to change from how it was before we had sensors. It can accept joystick input and handle it as joystick, including sensors. We could have a single joystick for all sensors or we can have a joystick per sensor. I think the latter way is better ..


Ok. I have 2 questions related to creating and using multiple joysticks. In the constructor for a joystick
[java]
public Joystick(InputManager inputManager, JoyInput joyInput,
int joyId, String name, int buttonCount, int axisCount,
int xAxis, int yAxis){
[/java]
there is an axisCount which will be "3" for sensors. There is also an xAxis and yAxis defined. I believe I'll have to add a zAxis as well as a getZAxisIndex method in JoyStick to support 3 axis Joysticks (ie Sensor Data). Is that ok?

Also, How will the user differentiate between the "Orientation" joystick and the "Direction" joystick in the app? The trigger for a Joystick contains a JoyID and an Axis. Should there be some constants defined somewhere so that the trigger can specifiy a specific simulated joystick type? Right now, I think the user just has to know somehow that on Android joyID = 0 is for Orientation, joyID = 1 is for Direction, etc. What do you think?

I just removed the previous sensor support. I’m starting to prepare the simulated joystick method.


@wezrule said:
cant wait for this!!! i'm definitely gonna use it when I start my new project next month. If you need help with it, or documenting it, need tutorials written etc then let me know


I think I will need some help figuring out how to convert the Android Orientation Coordinate System into the Application Coordinates. In Android, the Orientation is relative to Earth while the orientation we want is relative to the World Coordinates in the App.

Here are a couple of links that are relavent.

Magnegic and Acceleration Sensor Coordinates
Orientation Coordinates
There is also an xAxis and yAxis defined. I believe I’ll have to add a zAxis as well as a getZAxisIndex method in JoyStick to support 3 axis Joysticks (ie Sensor Data). Is that ok?

I think it is okay to add an additional zAxis in the Joystick constructor.
Perhaps the joystick name should match what kind of sensor it is. So it could be something like "Device Orientation", etc.

I think I will need some help figuring out how to convert the Android Orientation Coordinate System into the Application Coordinates. In Android, the Orientation is relative to Earth while the orientation we want is relative to the World Coordinates in the App.

For Y-up, the device's Y-up should match the jME3 world Y-up. This of course means you may need to apply a transform to it as it will be 90 degrees off in landscape mode.
For the other axes, remember that the joystick sends deltas and not absolute values. If the app is keeping track of the absolute value by aggregating the sensor deltas, it can just set the value to zero on calibration.

I think it is okay to add an additional zAxis in the Joystick constructor.
Perhaps the joystick name should match what kind of sensor it is. So it could be something like "Device Orientation", etc.

I was planning on doing that. So, without a constant to use, the user will have to iterate through the available joysticks returned from inputManager.getJoysticks() and if the joystick.getName() matches the sensor type they want via a String match, they add a trigger with joystick.assignAxis(). I would prefer to have the user match the sensor with a constant or enum type, but I'll do it with a String match on the joystick.getName for now.


For Y-up, the device's Y-up should match the jME3 world Y-up. This of course means you may need to apply a transform to it as it will be 90 degrees off in landscape mode.
For the other axes, remember that the joystick sends deltas and not absolute values. If the app is keeping track of the absolute value by aggregating the sensor deltas, it can just set the value to zero on calibration.

Yep. That is the plan. As long as I can figure out the transform to use from the sensor coordinate system (relative to Earth) to the jME3 world coordinate system. Android actually has a method call to get the current device rotation in 90deg increments so adjusting for the devices current mode (landscape vs portrait) can be accounted for.

Just wanted to check in and let you know I haven’t forgot about this. I took some time away but am back. The basic structure is done to provide sensor feedback to the user via the Joystick interface (both RawInputListener and onAnalog). I should be able to check in the changes next week sometime (or maybe sooner). Below is an example program that uses the sensor data via RawInputListener. I make another example of using it with onAnalog soon.



The sensor data is provided as a delta orientation change in radians per axis. The orientation of the screen (landscape or portrait, etc.) is accounted for so that the data Y axis is always pointing UP relative to the screen orientation.



I’m not a big fan of how the user needs to know the Joystick name to use to determine what kind of sensor data is being returned or the fact that the InputManager multiplies the value by TPF before sending the data to onAnalog, but it is what it is for now.



More to come…



[java]

/**

  • Test Case for using Android Sensors as Simulated Joysticks.
  • Includes methods for using both Raw Data from RawInputListener as well as
  • registered Mappings/Triggers for onAnalog. Values returned from both
  • RawInputListener and onAnalog are delta Orientation values in radians from
  • the last update. InputManager internally multiples the joystick value by
  • TPF before returning them in onAnalog, so if using onAnalog, divide the
  • value by TPF to get back to the orientation change in radians.

    *
  • @author iwgeric

    /

    public class Main extends SimpleApplication implements ActionListener, AnalogListener {

    private static final Logger logger = Logger.getLogger(Main.class.getName());

    private Geometry geomZero = null;



    private final float[] deltaRawOrientation = new float[3];

    private final float[] curRawOrientation = new float[3];

    private final float[] startingRawOrientation = new float[3];



    private Joystick[] joysticks;

    private IntMap<Joystick> joystickMap = new IntMap<Joystick>();



    private final String ORIENTATION_SENSOR = "Device Orientation";

    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";



    public static void main(String[] args) {

    Main app = new Main();

    AppSettings settings = new AppSettings(true);

    settings.setUseJoysticks(true);

    app.setSettings(settings);

    app.start();

    }



    private float toDegrees(float rad) {

    return rad
    FastMath.RAD_TO_DEG;

    }

    private float toRadians(float deg) {

    return deg*FastMath.DEG_TO_RAD;

    }



    @Override

    public void simpleInitApp() {

    flyCam.setDragToRotate(true);

    flyCam.setEnabled(false);



    Box b = new Box(Vector3f.ZERO, 1, 1, 1);

    geomZero = new Geometry("Box", b);



    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

    mat.setColor("Color", ColorRGBA.Blue);

    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);



    inputManager.addMapping("MouseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

    inputManager.addListener(this, "MouseClick");



    joysticks = inputManager.getJoysticks();

    if (joysticks == null) {

    logger.log(Level.INFO, "Cannot find any joysticks!");

    } else {

    for (int i = 0; i < joysticks.length; i++){

    Joystick joystick = joysticks;

    logger.log(Level.INFO, "Joystick: {0}", joystick.toString());



    if (joystick.getName().equalsIgnoreCase(ORIENTATION_SENSOR)) {

    logger.log(Level.INFO, "Found {0} Joystick, assigning mapping for X axis: {1}",

    new Object[]{ORIENTATION_SENSOR, joystick.getXAxisIndex()});

    joystick.assignAxis(ORIENTATION_X_PLUS, ORIENTATION_X_MINUS, joystick.getXAxisIndex());

    inputManager.addListener(this, ORIENTATION_X_PLUS, ORIENTATION_X_MINUS);

    joystickMap.put(joystick.getJoyIndex(), joystick);

    logger.log(Level.INFO, "Found {0} Joystick, assigning mapping for Y axis: {1}",

    new Object[]{ORIENTATION_SENSOR, joystick.getYAxisIndex()});

    joystick.assignAxis(ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS, joystick.getYAxisIndex());

    inputManager.addListener(this, ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS);

    joystickMap.put(joystick.getJoyIndex(), joystick);

    }

    }

    }



    inputManager.setAxisDeadZone(0.001f);

    inputManager.addRawInputListener(new RawInputListener() {



    public void onJoyAxisEvent(JoyAxisEvent jae) {

    Joystick joystick = joystickMap.get(jae.getJoyIndex());

    if (joystick != null) {

    if (joystick.getName().equalsIgnoreCase(ORIENTATION_SENSOR)) {

    if (jae.getAxisIndex() == 0) {

    // logger.log(Level.INFO, "Raw JoyAxisEvent for {0}, JoyIndex: {1}, AxisIndex: {2}, Value: {3}",

    // new Object[]{joystick.getName(), jae.getJoyIndex(), jae.getAxisIndex(), jae.getValue()});

    deltaRawOrientation[0] = jae.getValue();

    curRawOrientation[0] += jae.getValue();

    }

    if (jae.getAxisIndex() == 1) {

    // logger.log(Level.INFO, "Raw JoyAxisEvent for {0}, JoyIndex: {1}, AxisIndex: {2}, Value: {3}",

    // new Object[]{joystick.getName(), jae.getJoyIndex(), jae.getAxisIndex(), jae.getValue()});

    deltaRawOrientation[1] = jae.getValue();

    curRawOrientation[1] += jae.getValue();

    }

    if (jae.getAxisIndex() == 2) {

    // logger.log(Level.INFO, "Raw JoyAxisEvent for {0}, JoyIndex: {1}, AxisIndex: {2}, Value: {3}",

    // new Object[]{joystick.getName(), jae.getJoyIndex(), jae.getAxisIndex(), jae.getValue()});

    deltaRawOrientation[2] = jae.getValue();

    curRawOrientation[2] += jae.getValue();

    }

    }

    }

    }



    public void beginInput() {}



    public void endInput() {}



    public void onJoyButtonEvent(JoyButtonEvent jbe) {}



    public void onMouseMotionEvent(MouseMotionEvent mme) {}



    public void onMouseButtonEvent(MouseButtonEvent mbe) {}



    public void onKeyEvent(KeyInputEvent kie) {}



    public void onTouchEvent(TouchEvent te) {}

    });

    }



    @Override

    public void simpleUpdate(float tpf) {

    logger.log(Level.INFO, "lastRawOrientation: {0}, {1}, {2}, deltaRawOrientation: {3}, {4}, {5}",

    new Object[]{

    curRawOrientation[0], curRawOrientation[1], curRawOrientation[2],

    deltaRawOrientation[0], deltaRawOrientation[1], deltaRawOrientation[2],

    });

    logger.log(Level.INFO, "lastRawOrientation(deg): {0}, {1}, {2}, deltaRawOrientation(deg): {3}, {4}, {5}",

    new Object[]{

    toDegrees(curRawOrientation[0]), toDegrees(curRawOrientation[1]), toDegrees(curRawOrientation[2]),

    toDegrees(deltaRawOrientation[0]), toDegrees(deltaRawOrientation[1]), toDegrees(deltaRawOrientation[2])

    });



    geomZero.setLocalRotation(new Quaternion(new float[] {curRawOrientation[0], curRawOrientation[1], 0}));

    }



    public void onAction(String string, boolean pressed, float tpf) {



    if (string.equalsIgnoreCase("MouseClick") && pressed) {

    curRawOrientation[0] = curRawOrientation[1] = curRawOrientation[2] = 0;

    geomZero.setLocalRotation(new Quaternion(0f, 0f, 0f, 1f));

    geomZero.setLocalTranslation(0f, 0f, 0f);

    }

    }



    public void onAnalog(String string, float value, float tpf) {

    if (string.equalsIgnoreCase(ORIENTATION_X_PLUS)) {

    }

    if (string.equalsIgnoreCase(ORIENTATION_X_MINUS)) {

    }

    if (string.equalsIgnoreCase(ORIENTATION_Y_PLUS)) {

    }

    if (string.equalsIgnoreCase(ORIENTATION_Y_MINUS)) {

    }

    }



    }

    [/java]
2 Likes

mhh I don’t think it’s the right way.

Shouldn’t use analog listeners instead of a raw listener?

@nehon. No worries. It supports using onAnalog or RawInputListener, either one. I’ll post the onAnalog example soon.

ok :wink:

nice.

@nehon @Momoko_Fan



I’ve been playing around with the new sensor inputs using the onAnalog. It appears that InputManager does some scaling on “value” before calling onAnalog.



If the input is a mouse motion event, InputManager takes the value and divides by 1024 before passing it to onAnalog

If the input is a joystick axis event, InputManager takes the value and multiplies by tpf before passing it to onAnalog



Any idea why it scales “value” before passing it to onAnalog?

Any idea why it scales mouse motion and joystick axis differently?

@wezrule @nehon @Momoko_Fan and others



I just committed sensor support that sends the data to the application as a simulated joystick. The values returned in onAnalog should match what is sent when an actual joystick is used on the PC version of the application.



I only added orientation for now. If it works as everyone likes for orientation, I’ll add more sensors.



I’ll wait a couple of days to see if anyone has any major issues with this version and then I’ll create a new post to announce the support.



In MainActivity, you need to add the following line to enable sensors:

[java]

joystickEventsEnabled = true;

[/java]



My test project is below. Allows for setting an absolute rotation on an object or an incremental rotation based on the device orientation.

[java]

package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.input.Joystick;

import com.jme3.input.MouseInput;

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.system.AppSettings;

import com.jme3.texture.Texture;

import com.jme3.util.IntMap;

import java.util.logging.Level;

import java.util.logging.Logger;



public class Main extends SimpleApplication implements ActionListener, AnalogListener {

private static final Logger logger = Logger.getLogger(Main.class.getName());

private Geometry geomZero = null;



// variables to save the current rotation

private float[] anglesCurrent = new float[] {0f, 0f, 0f};

private float[] anglesCalibrated = new float[] {0f, 0f, 0f};

private Quaternion rotationQuat = new Quaternion();



// switch to use to apply an absolute rotation (geometry.setLocalRotation) or

// an incremental constant rotation (geometry.rotate)

private boolean useAbsolute = true;



// rotation speed to use when apply constant incremental rotation

private float rotationSpeedX = 1f;

private float rotationSpeedY = 1f;



// flag to record the starting orientation when using absolute rotations

private boolean recordCalibration = false;



// Map of joysticks saved with the joyId as the key

private IntMap<Joystick> joystickMap = new IntMap<Joystick>();



// name of joystick when Android is using device orientation

private final String ORIENTATION_SENSOR = “Device Orientation”;



// 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”;



// Make sure to set joystickEventsEnabled = true in MainActivity for Android



public static void main(String[] args) {

Main app = new Main();

AppSettings settings = new AppSettings(true);

settings.setUseJoysticks(true);

app.setSettings(settings);

app.start();

}



private float toDegrees(float rad) {

return rad*FastMath.RAD_TO_DEG;

}



@Override

public void simpleInitApp() {

flyCam.setDragToRotate(true);



// flyCam also uses joysticks to rotate the camera, so disable it for

// this example

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)));



lineX.setLineWidth(2);

lineX.setLineWidth(2);

lineX.setLineWidth(2);



Geometry geoX = new Geometry(“X”, lineX);

Material matX = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

matX.setColor(“Color”, ColorRGBA.Red);

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);

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);

geoZ.setMaterial(matZ);

rootNode.attachChild(geoZ);



Box b = new Box(Vector3f.ZERO, 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 {

// Android Orientation is defined as the first joystick (joystick[0])

// so it can use the same mapping as the PC version of the app if

// a single actual joystick is connected.

// 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);

// Joystick joystick = joysticks[0];

for (Joystick joystick: joysticks) {



// You could do a custom rotation speed and mappings based

// on using the device orientation vs an acutal joystick

if (joystick.getName().equalsIgnoreCase(ORIENTATION_SENSOR)) {

// Orientation Sensor found

rotationSpeedX = 2f;

rotationSpeedY = 2f;



logger.log(Level.INFO, “Found {0} Joystick, assigning mapping for X axis: {1}”,

new Object[]{joystick.toString(), joystick.getXAxisIndex()});

joystick.assignAxis(ORIENTATION_X_PLUS, ORIENTATION_X_MINUS, joystick.getXAxisIndex());

inputManager.addListener(this, ORIENTATION_X_PLUS, ORIENTATION_X_MINUS);

joystickMap.put(joystick.getJoyId(), joystick);



logger.log(Level.INFO, “Found {0} Joystick, assigning mapping for Y axis: {1}”,

new Object[]{ORIENTATION_SENSOR, joystick.getYAxisIndex()});

joystick.assignAxis(ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS, joystick.getYAxisIndex());

inputManager.addListener(this, ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS);

joystickMap.put(joystick.getJoyId(), joystick);



// adjust the deadzone for joysticks, if desired.

inputManager.setAxisDeadZone(0.003f);



} else {

// Normal joystick being used

rotationSpeedX = 1f;

rotationSpeedY = 1f;



logger.log(Level.INFO, “Found {0} Joystick, assigning mapping for X axis: {1}”,

new Object[]{joystick.toString(), joystick.getXAxisIndex()});

joystick.assignAxis(ORIENTATION_X_PLUS, ORIENTATION_X_MINUS, joystick.getXAxisIndex());

inputManager.addListener(this, ORIENTATION_X_PLUS, ORIENTATION_X_MINUS);

joystickMap.put(joystick.getJoyId(), joystick);



logger.log(Level.INFO, “Found {0} Joystick, assigning mapping for Y axis: {1}”,

new Object[]{ORIENTATION_SENSOR, joystick.getYAxisIndex()});

joystick.assignAxis(ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS, joystick.getYAxisIndex());

inputManager.addListener(this, ORIENTATION_Y_PLUS, ORIENTATION_Y_MINUS);

joystickMap.put(joystick.getJoyId(), joystick);



// adjust the deadzone for joysticks, if desired.

inputManager.setAxisDeadZone(0.05f);

}



}



}



}



@Override

public void simpleUpdate(float tpf) {



if (recordCalibration) {



logger.log(Level.INFO, “Resetting orientation back to zero”);

if (useAbsolute) {

anglesCalibrated[0] = anglesCalibrated[0] + anglesCurrent[0];

anglesCalibrated[1] = anglesCalibrated[1] + anglesCurrent[1];

anglesCalibrated[2] = anglesCalibrated[2] + anglesCurrent[2];

}



anglesCurrent[0] = anglesCurrent[1] = anglesCurrent[2] = 0f;

geomZero.setLocalRotation(Quaternion.IDENTITY);



recordCalibration = false;



} else {



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) {

recordCalibration = true;

}

}



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) - anglesCalibrated[0];

} 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) - anglesCalibrated[0];

} 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) - anglesCalibrated[1];

} 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) - anglesCalibrated[1];

} else {

// apply an incremental rotation amount based on rotationSpeed

anglesCurrent[1] -= scaledValue * rotationSpeedY;

}

}



}



}



[/java]

2 Likes

very nice!

I’m not at home for 2 weeks so I can’t test, could you possibly drop an apk somewhere so i can test on my phone?

ah awesome, will have to test this when im back home tonight

just tested it, and can confirm it works and responds nicely although:



1 - You use the monkey jpg which isn’t a standard included asset, so i had to include it myself

2 - There’s a bit of jittering when i try and hold it steady, are you using a low-pass filter or anything?

3 - Rotating around the y axis works perfectly

4 - It rotates along the -x axis perfect, but the +ve x fails completely sometimes

5 - Rotation around the z (sterring wheel turning), doesn’t do as i would expect it, but it is quite smooth



Thanks again!

@wezrule

Thanks for taking a look. What do you mean by the +ve x fails?

In my example, I’m only using the x and y axis from the sensor. Are you using this or your own Main.java file?

i’m using your one. Ok that explains why the Z axis doesn’t look as it should.



Everything works awesome, when the phone is face up and parallel to the ground, but when I have it almost at 90deg around x axis (so the screen is almost perpendicular to the floor, facing me), and rotate the cube around the -ve jME axis it seems to go quite crazy, ahh its when it reaches almost perpendicular that the angles are screwing up, that would explain it.



There is a bit of general jitter sometimes as well, around the x axis near the origin



But good job, thanks for the hard work!

Yeah, I think the sensors start returning some rotated values when the device is rotated close to 90deg around X (+y gets close to pointing straight up). Not sure I can do much about that.



The Z axis reports back the orientation of the +y axis from magnetic north. Since typical joysticks are 2 axes, I didn’t put the Z axis in the example. If you create your own app, you should be able to create a mapping for the 3rd axis manually to test it out (joy axis 2). You would have to record the first value in the app and calculate the delta before using it so that you don’t get a large value in the first scan (depending on which direction your standing on startup).



The filter might be a good idea. I think I set the dead zone in inputManager to 0.003 instead of the default 0.05. Setting it a little bigger would also help the jitter while the device is a zero.