JM3 – Missing something or setLocalTranslation bug?

Hello!



I use jMonkey3, Osculator and a Wiimote. Osculator connects a Wiimote with my mac and translates wiiMote output as OSC commands (with a lot of imagination, it is like xml). With wiimote I try to move (and I do) a shadow figure and from OSCulator I have set the wiimote roll element to send values form -85 to 85 (consider them as degrees).



I use the following line of code to accomplish the rotation

this.nm.getNionios().setLocalRotation( new Quaternion(). fromAngleAxis((float) Math.toRadians(tmpRotateValueRoll), new Vector3f(0,0,1)));



tmpRotateValueRoll has a value from wiimote between -85 and 85.



However the behaviour is not the expected one. If I roll the wiimote to any direction and keep it to a any angle for a long time without any movement, the figure doen’t stay still but occasionally (say one time per second on average) rotates to the opposite direction alone!



You should better take a look at the following screenshots which were taken in less than a second and look specifically at the red eclipses.



http://img695.imageshack.us/f/screenshot20100920at120.png/

http://img688.imageshack.us/f/screenshot20100920at120.png/



In these photos I have left WiiMote on my desk still rolled in 90 degrees to the right. As you can see the values do not justify the sudden rotation of the figure (its like a mirrored rotation).



So it is a bug or am I missing something?

I guess the wiimote osc commands are processed on another thread. Do you enqueue the changes to the scenegraph properly? (see snippets part of this forums group)

Thanks for the quick response! Yes you guessed right. There is a separate thread (not mine) which is responsible for osc commands. So for each osc command I have to implement the appropriate listener. Actually I have to implement the acceptMessage method in which I say what I want to do when I receive a specific osc command.



Here is the whole code for the listener (just in case) responsible for the movevent in x - y axis and for roll.



/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package guitesting.WiiOSC;



    import com.illposed.osc.OSCMessage;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import java.util.Date;

    import java.text.DecimalFormat;

    import main.ShadowMove;



    /**

    *
  • @author andreas

    */

    public class HDMoveListener extends WiiOSCListener

    {

    private ShadowMove nm;

    private static float previousValueRoll = 0;

    private static float previousValueY = 0;

    private static float previousValueX = 0;

    static boolean shadowMovingLeft = true;



    public HDMoveListener()

    {

    super();

    this.nm = null;

    }



    public HDMoveListener(ShadowMove nm)

    {

    super();

    this.nm = nm;

    }



    @Override

    public void acceptMessage(Date date, OSCMessage oscm)

    {

    super.newOscMessageReceived();



    Object oscArgs[] = oscm.getArguments();

    DecimalFormat df = new DecimalFormat("###.###");



    float moveValueX = (Float) oscArgs[2];

    float moveValueY = (Float) oscArgs[0];

    float rotateValueRoll = Float.parseFloat(df.format((Float) oscArgs[1]));



    float tmpMoveValueX = moveValueX;

    float tmpMoveValueY = moveValueY;

    float tmpRotateValueRoll = rotateValueRoll;



    if(HDMoveListener.previousValueX!= moveValueX)

    this.nm.getNionios().setLocalTranslation(tmpMoveValueX, this.nm.getNionios().getLocalTranslation().getY(), this.nm.getNionios().getLocalTranslation().getZ());



    if(HDMoveListener.previousValueY!= moveValueY)

    this.nm.getNionios().setLocalTranslation(this.nm.getNionios().getLocalTranslation().getX(), tmpMoveValueY, this.nm.getNionios().getLocalTranslation().getZ());





    if(HDMoveListener.shadowMovingLeft)

    {



    if ((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0))

    ;

    else

    tmpRotateValueRoll *= -1;

    }

    else

    {

    if (!((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0)))

    ;

    else

    tmpRotateValueRoll *= -1;

    }



    if(HDMoveListener.previousValueRoll != rotateValueRoll)

    this.nm.getNionios().setLocalRotation( new Quaternion(). fromAngleAxis((float) Math.toRadians(tmpRotateValueRoll), new Vector3f(0,0,1)));



    System.out.println(" Roll radian values: " + (float) Math.toRadians(Float.parseFloat(df.format((Float) oscArgs[1]))) + " WiiMote roll degree values: " + Float.parseFloat(df.format((Float) oscArgs[1])));



    HDMoveListener.previousValueY = moveValueY;

    HDMoveListener.previousValueX = moveValueX;

    HDMoveListener.previousValueRoll = rotateValueRoll;

    }

    }




    I suppose I don’t enqueue the changes to the scenegraph properly but I also didn’t understand how to do it from the snippet (I suppose you were referring to “Modifying objects from another Thread (jME3)”)

Yes I was. You have to make the value final and then set the location where the modifyObject() method is called in the example code.

Which value I have to make final? tmpRotateValueRoll? I still don’t understand what I have to do with modifyObject. I am not so experienced (and smart) so can you tell me where I can read more about this stuff?



Thank you for your willing to help so far!

Sure, just google “java concurrency” and/or “java callable” to get more info. Basically the callable is just an object that has one method that is executed on another thread (call()). So you make your rotation value from the wii a final variable (so that it doesnt change anymore while being processed by the other thread) and then create a callable with it to set the value in jme. You dont need to use the get() method like in the example. This way the command will simply be executed “fire and forget” style, w/o waiting for the call method to finish.

Cheers,

Normen

Ok! I will take a look at those stuff and if I get stuck I’ll ask again! Thank you!

I hadn’t the time to look the theory you have suggested me yet, but thinking of that I have a question. Why this “thread problem thing” doesn’t causes the same trouble in the setLocalTranslation too, but only in the setLocalRotation? I mean why does my figure moves smoothly without any problem in x/y axises while in rotation I have that shaking? The code is similar and it is in the same method that I posted above, so there are treated by the same thread each time.

You simply should not modify an object from two threads at the same time. Imagine this: The OSC thread calls the acceptMessage() method and directly modifies the rotation Matrix or Quaternion of the Spatial. It consists of 4 or more float values. The OSC thread starts writing these and has written only 2 of them when the OpenGL thread comes and reads all 4 of them to paint the Spatial → wrong rotation.



The vector value for the location is more simple and a slightly different value in one of the tuples values is not really visible (like having the two new x+y coords but not the new z one). The rotation matrix is a bit more complicated than that and might spew out quite different results when you change one of the values.



So you need to have control over what thread modifies the Spatials and it should be the OpenGL thread. This is why you send it a list of Callables that are executed on the OpenGL thread and do the modifications there before the Spatial is painted.



Hope this helps,

Normen

Yes! It definitely helped what you said! Thank you very much!

Hi!



Due to some other things I have left my thread problem but now I am back and I have questions, if anyone can help!



I have a better understanding about callable and future interfaces and I understand the snippet too. However I have a difficulty to apply the theory in my code.



So I have a class called ShadowMove where I initialize characters, stage etc. Inside [java]public void simpleInitApp() [/java]I also have the following code for the OSC messages:



[java]

try

{

OSCPortIn receiver;

receiver = new OSCPortIn(1119);

PressAListener pressAListener = new PressAListener(this);

PressBListener pressBListener = new PressBListener(this);

HDMoveListener hdMoveListener = new HDMoveListener(this);



receiver.addListener("/pressA", pressAListener);

receiver.addListener("/pressB", pressBListener);

receiver.addListener("/hdMove", hdMoveListener);



receiver.startListening();

}

catch (SocketException ex)

{

Logger.getLogger(ShadowMove.class.getName()).log(Level.SEVERE, null, ex);

}[/java]



and implementations of the above (…)Listener classes. That’s all of my code. I have post the implementation of HDMoveListener in a previous post. That’s my code. The other OSC code is not mine (OSCPortIn), it is from a java OSC api I found. However I have access to the osc code since I use the actual source code and not any .jar file.



So which class should implement the Callable interface? Or should I create a new one to implement the Callable interface? The “enqueue” code from the snippet where should be placed? Any hints?

Just do it like this when you modify the jME objects:

[snippet id=“10”]

I’m a little closer in understanding but still confused. Thank you for your patience so far!


  1. So the modifyObject() method you mentioned is the code I placed inside public void acceptMessage(Date date, OSCMessage oscm) method, right?
  2. In that case I should return the spatial, right?
  3. The “application” in the snippet is my ShadowMove class since it extends SimpleBulletApplication and the spatial is attached to the rootNode, right?



    Assuming that these questions have a positive answer I tried to make the changes but I failed again (Ι got used to that… ). I placed the acceptmessage() code inside call() implementation and I returned the spatial. Obviously I am wrong but I don’t know where.



    Here is the new HDMoveListener code just for the record.



    [java]

    package guitesting.WiiOSC;



    import com.illposed.osc.OSCMessage;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Spatial;

    import java.util.Date;

    import java.text.DecimalFormat;

    import java.util.concurrent.Callable;

    import java.util.concurrent.ExecutionException;

    import java.util.concurrent.Future;

    import java.util.logging.Level;

    import java.util.logging.Logger;

    import main.ShadowMove;



    /**

    *
  • @author andreas

    */

    public class HDMoveListener extends WiiOSCListener

    {

    private ShadowMove nm;



    private static float previousValueRoll = 0;

    private static float previousValueY = 0;

    private static float previousValueX = 0;

    static boolean shadowMovingLeft = true;



    protected static Spatial tempShadowPuppet;

    protected static Object oscArgs[];



    public HDMoveListener()

    {

    super();

    this.nm = null;

    }



    public HDMoveListener(ShadowMove nm)

    {

    super();

    this.nm = nm;

    }



    @Override

    public void acceptMessage(Date date, OSCMessage oscm)

    {

    super.newOscMessageReceived();



    HDMoveListener.tempShadowPuppet = this.nm.getNionios();

    HDMoveListener.oscArgs = oscm.getArguments();



    Future fut = this.nm.enqueue(new Callable()

    {

    public Spatial call() throws Exception

    {

    DecimalFormat df = new DecimalFormat("###.###");



    float moveValueX = (Float) HDMoveListener.oscArgs[2];

    float moveValueY = (Float) HDMoveListener.oscArgs[0];

    final float rotateValueRoll = Float.parseFloat(df.format((Float) HDMoveListener.oscArgs[1]));



    float tmpMoveValueX = moveValueX;

    float tmpMoveValueY = moveValueY;

    float tmpRotateValueRoll = rotateValueRoll;



    if (HDMoveListener.previousValueX != moveValueX)

    {

    HDMoveListener.tempShadowPuppet.setLocalTranslation(tmpMoveValueX, HDMoveListener.tempShadowPuppet.getLocalTranslation().getY(), HDMoveListener.tempShadowPuppet.getLocalTranslation().getZ());

    }



    if (HDMoveListener.previousValueY != moveValueY)

    { HDMoveListener.tempShadowPuppet.setLocalTranslation(HDMoveListener.tempShadowPuppet.getLocalTranslation().getX(), tmpMoveValueY, HDMoveListener.tempShadowPuppet.getLocalTranslation().getZ());

    }



    if (HDMoveListener.shadowMovingLeft)

    {

    if ((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0));

    else

    {

    tmpRotateValueRoll *= -1;

    }

    }

    else

    {

    if (!((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0)));

    else

    {

    tmpRotateValueRoll *= -1;

    }

    }



    if (HDMoveListener.previousValueRoll != rotateValueRoll)

    {

    HDMoveListener.tempShadowPuppet.setLocalRotation(new Quaternion().fromAngleAxis((float) Math.toRadians(tmpRotateValueRoll), new Vector3f(0, 0, 1)));

    }



    HDMoveListener.previousValueY = moveValueY;

    HDMoveListener.previousValueX = moveValueX;

    HDMoveListener.previousValueRoll = rotateValueRoll;



    return HDMoveListener.tempShadowPuppet;

    }

    });

    try

    {

    //to retrieve return value (waits for call to finish, fire&forget otherwise):

    fut.get();

    }

    catch (InterruptedException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    catch (ExecutionException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    }

    }

    [/java]

No, you have to put the whole shenaningan into your accept method. The modifyObject method is just an example for mySpatial.setLocalTranslation() for example.

You mean something like the following code? I am asking about the general idea.

[java]

if (HDMoveListener.previousValueRoll != rotateValueRoll)

{

Future fut = this.nm.enqueue(new Callable()

{

public Object call() throws Exception

{

HDMoveListener.tempShadowPuppet.setLocalRotation(new Quaternion().fromAngleAxis((float) Math.toRadians(tmpRotateValueRoll), new Vector3f(0, 0, 1)));

return HDMoveListener.tempShadowPuppet;

}

});

try

{

//to retrieve return value (waits for call to finish, fire&forget otherwise):

fut.get();

}

catch (InterruptedException ex)

{

Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

}

catch (ExecutionException ex)

{

Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

}

}

[/java]



I know it has errors but I ran it by curiosity and had this message too

[java]java.lang.IllegalStateException:

Scene graph is not properly updated for rendering.

Make sure scene graph state was not changed after rootNode.updateGeometricState() call.[/java] .



I have another question since you are more familiar with callable interface etc. I saw here in the second paragraph that I can have a callable with no return value which seems more appropriate to me since mySpatial.setLocalRotation has void return type. However whatever I have tried, netbeans marked it as error. Is it possible here or another way has to be found?

The code looks good, but it seems you are still changing some spatials location outside of the render loop, hence the error.

About the error, you can have the metod return the “Void” (not “void”) type, but you will always have to return something (just return null in this case) in that method.

Cheers,

Normen

Yes indeed I have done that only in the setLocalRotation and not in the setLocalTranslation and now I don’t have that exception. I changed the Object return type to Void as you have said and I return null value. I still have the shaking though. What is missing? Here is the full code with the changes:

[java]

/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.

    */

    package guitesting.WiiOSC;



    import com.illposed.osc.OSCMessage;

    import com.jme3.math.Quaternion;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Spatial;

    import java.util.Date;

    import java.text.DecimalFormat;

    import java.util.concurrent.Callable;

    import java.util.concurrent.ExecutionException;

    import java.util.concurrent.Future;

    import java.util.logging.Level;

    import java.util.logging.Logger;

    import main.ShadowMove;



    /**

    *
  • @author andreas

    */

    public class HDMoveListener extends WiiOSCListener

    {



    private ShadowMove nm;

    private static float previousValueRoll = 0;

    private static float previousValueY = 0;

    private static float previousValueX = 0;

    static boolean shadowMovingLeft = true;

    protected static Spatial tempShadowPuppet;

    protected static Object oscArgs[];



    public HDMoveListener()

    {

    super();

    this.nm = null;

    }



    public HDMoveListener(ShadowMove nm)

    {

    super();

    this.nm = nm;

    }



    @Override

    public void acceptMessage(Date date, OSCMessage oscm)

    {

    HDMoveListener.tempShadowPuppet = this.nm.getNionios();

    super.newOscMessageReceived();



    HDMoveListener.oscArgs = oscm.getArguments();





    DecimalFormat df = new DecimalFormat("###.###");



    float moveValueX = (Float) HDMoveListener.oscArgs[2];

    float moveValueY = (Float) HDMoveListener.oscArgs[0];

    final float rotateValueRoll = Float.parseFloat(df.format((Float) HDMoveListener.oscArgs[1]));



    final float tmpMoveValueX = moveValueX;

    final float tmpMoveValueY = moveValueY;

    final float tmpRotateValueRoll;



    if (HDMoveListener.previousValueX != moveValueX)

    {

    Future fut = this.nm.enqueue(new Callable()

    {

    public Void call() throws Exception

    {

    HDMoveListener.tempShadowPuppet.setLocalTranslation(tmpMoveValueX, HDMoveListener.tempShadowPuppet.getLocalTranslation().getY(), HDMoveListener.tempShadowPuppet.getLocalTranslation().getZ());

    return null;

    }

    });

    try

    {

    //to retrieve return value (waits for call to finish, fire&forget otherwise):

    fut.get();

    }

    catch (InterruptedException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    catch (ExecutionException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    }



    if (HDMoveListener.previousValueY != moveValueY)

    {

    Future fut = this.nm.enqueue(new Callable()

    {

    public Void call() throws Exception

    {

    HDMoveListener.tempShadowPuppet.setLocalTranslation(HDMoveListener.tempShadowPuppet.getLocalTranslation().getX(), tmpMoveValueY, HDMoveListener.tempShadowPuppet.getLocalTranslation().getZ());

    return null;

    }

    });

    try

    {

    //to retrieve return value (waits for call to finish, fire&forget otherwise):

    fut.get();

    }

    catch (InterruptedException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    catch (ExecutionException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    }





    if (HDMoveListener.shadowMovingLeft)

    {

    if ((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0))

    tmpRotateValueRoll = rotateValueRoll;

    else

    tmpRotateValueRoll = -1 * rotateValueRoll;

    }

    else

    {

    if (!((HDMoveListener.previousValueRoll > rotateValueRoll && rotateValueRoll > 0)

    || (HDMoveListener.previousValueRoll < rotateValueRoll && rotateValueRoll < 0)))

    tmpRotateValueRoll = rotateValueRoll;

    else

    tmpRotateValueRoll = -1 * rotateValueRoll;

    }



    if (HDMoveListener.previousValueRoll != rotateValueRoll)

    {

    Future fut = this.nm.enqueue(new Callable()

    {

    public Void call() throws Exception

    {

    HDMoveListener.tempShadowPuppet.setLocalRotation(new Quaternion().fromAngleAxis((float) Math.toRadians(tmpRotateValueRoll), new Vector3f(0, 0, 1)));

    return null;

    }

    });

    try

    {

    //to retrieve return value (waits for call to finish, fire&forget otherwise):

    fut.get();

    }

    catch (InterruptedException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    catch (ExecutionException ex)

    {

    Logger.getLogger(HDMoveListener.class.getName()).log(Level.SEVERE, null, ex);

    }

    }



    HDMoveListener.previousValueY = moveValueY;

    HDMoveListener.previousValueX = moveValueX;

    HDMoveListener.previousValueRoll = rotateValueRoll;

    }

    }

    [/java]

You are parsing and applying accelerometer data, right? Know that the accelerometer sensor in the wii-remote has a lot of noise, and any derivations from that data (roll/pitch) will also consequently have noise.

To determine rotation without noise, one way is to use the gyroscope (aka Wii Motion Plus).

No, I don’t parse accelerometer data, I have attached Wii Motion Plus to the Wiimote so from osculator I send pitch, yaw and roll values from the motion plus and not accelerometer. Thanks for pointing that out since I didn’t know that detail. I originally started using Motion Plus because I had “wrong” values from the accelerometer yaw due to gravity issues.

In your code I see you’re using moveValueX/Y to set local translation. So you’re also using accelerometer to integrate into position, right?

Does the shaking happen on the rotation or position?