Understanding World/Local transformations and updateGS

Hello,



In my simple game i have two boxes, sun and planet. When the user press "u" i want to attach sun to planet, rotate the sun HALF_PI and deattach the planet, maintaining its rotation.



But after the first rotation, the whole thing starts spinning very fast and stops on weird positions. Can anyone help me understand what am i doing wrong?



Runnable class on: http://crantz.googlecode.com/svn/trunk/rubico/src/Test.java



Relevant sections:



   private void startRotation() {
      rootNode.detachChild(planet);
      sun.attachChild(planet);      
   }




   private void endRotation() {
      planet.setLocalRotation(planet.getWorldRotation());
      planet.setLocalTranslation(planet.getWorldTranslation());
      sun.detachChild(planet);
      planet.updateGeometricState(tpf, false);
      rootNode.attachChild(planet);
   }




@Override
    protected void simpleUpdate() {
       if(KeyBindingManager.getKeyBindingManager().isValidCommand("u", false)){
          isRotating = true;
          initialAngle = angle;
          startRotation();
       }

       if(isRotating){          
          angle += tpf * FastMath.HALF_PI;
          sun.getLocalRotation().fromAngleAxis(-angle, Vector3f.UNIT_Y);
          if(angle - initialAngle > FastMath.HALF_PI){
             isRotating = false;
             endRotation();
          }
       }
    }

I think you might have found something interesting here… I managed to make it work, but I think something weird is happening, because the first time you press 'u', the local translation of the planet is not modified by the rotation to the sun (which is what you would expect), but the second time around, IT DOES!!!



So, in order to get around that, after rotating the sun, I just updated the local translation to (-50, 0, 0), and added an updateGeometricState before the endRotation…



Here is the code:


import com.jme.app.SimpleGame;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.LightState;

public class Test extends SimpleGame {
   Node sun = new Node( "Sun" );
   Node planet = new Node( "Planet" );
   float angle;
   float initialAngle;
   boolean isRotating;
   
   private void startRotation() {
      rootNode.detachChild(planet);
      sun.attachChild(planet);
      planet.setLocalRotation( new Quaternion() );
      planet.setLocalTranslation( -50f, 0f, 0f );
   }
   
   private void endRotation() {
      rootNode.updateGeometricState( 0f, true );
      planet.setLocalTranslation(planet.getWorldTranslation());
      planet.setLocalRotation(planet.getWorldRotation());
      sun.detachChild(planet);
      rootNode.attachChild(planet);
   }
   
    @Override
    protected void simpleUpdate() {
       if(KeyBindingManager.getKeyBindingManager().isValidCommand("u", false)){
          isRotating = true;
          initialAngle = angle;
          startRotation();
       }
       else if(isRotating){
          angle += tpf * FastMath.HALF_PI;
          sun.getLocalRotation().fromAngleAxis(-angle, Vector3f.UNIT_Y);
          planet.setLocalTranslation( -50f, 0f, 0f );
          if(angle - initialAngle > FastMath.HALF_PI){
             isRotating = false;
             endRotation();
          }
       }
       System.out.println( planet.getLocalTranslation() );
    }

   @Override
   protected void simpleInitGame() {
      cam.getLocation().set(0,20,150);   
      
      sun.attachChild(new Box("box", Vector3f.ZERO, 10, 10, 10));
      rootNode.attachChild(sun);      
      
      planet.attachChild(new Box("box", Vector3f.ZERO, 10, 10, 10));
      planet.getLocalTranslation().setX(-50);
      
      rootNode.attachChild(planet);
      rootNode.attachChild(sun);
      
      KeyBindingManager.getKeyBindingManager().add("u", KeyInput.KEY_U);       
   }
   
   public static void main(String[] args)
    {
        Test app = new Test();
        app.setDialogBehaviour(SimpleGame.NEVER_SHOW_PROPS_DIALOG);
        app.start();
    }
}



Maybe a dev could explain why the local translation is modified the second time around  :?

Thank you very much for your solution, i helps a lot.

But there is still one problem. The line 46 reads:



planet.setLocalTranslation( -50f, 0f, 0f );



In a simple scene as this, that is fine. But in a complex system, having to recalculate and reset local translations can be really problematic.

Also, this is the line that, if commented, makes it all spin weirdly.

I wonder why is that? The world->local copy should take care of this, did't it?

Yes, it should, but as I said, the system is behaving inconsistently with respect to local rotation on parent affecting local translation on child (it should affect world translation, but not the other one)



This made me realize that there must be something wrong with your references… The only way this would happen is if world AND local translations are REALLY the same object… So use this line at the end of your simpleUpdate method and you will see that they are true after pressing 'u' once!

       System.out.println( planet.getLocalTranslation() + " — " + (planet.getLocalTranslation() == planet.getWorldTranslation()) );





Therefore, you are ending the rotation in a bad way!.


   private void endRotation() {
      rootNode.updateGeometricState( 0f, true );
      planet.getLocalTranslation().set(planet.getWorldTranslation());  // DO NOT OVERWRITE LOCAL VALUES!!!!!!!!
      planet.setLocalRotation(planet.getWorldRotation());
      sun.detachChild(planet);
      rootNode.attachChild(planet);
   }



I am banging my head because I didn't see that before!!!! 

Doing

planet.setLocalTranslation(planet.getWorldTranslation());

instead of

planet.getLocalTranslation().set(planet.getWorldTranslation());



is probalby one of the most common misstakes you can do when starting with jme.

I did it to in the first jme code I wrote and did my fair share of head banging :slight_smile:

Nice, that explains a lot, thank you very much.

I just do not understand why i have to do:



planet.setLocalTranslation( -50f, 0f, 0f );



in startRotation(). Resetting the original position before every rotation sounds weird, but it is the only way it works. why is that? Cant

Oh, no, you don't have to do that anymore…  :wink:

Sorry about the necroposting, but I have tried his code and after having made the changes I still had to put the planet.setLocalTranslation( -50f, 0f, 0f ); on start rotating for it to work.

I cant understand why could this happened…

I am asking this cause I am also trying to work work with node rotations :S

obidobi said:

Doing
planet.setLocalTranslation(planet.getWorldTranslation());
instead of
planet.getLocalTranslation().set(planet.getWorldTranslation());

is probalby one of the most common misstakes you can do when starting with jme.
I did it to in the first jme code I wrote and did my fair share of head banging :)


oh learned sth new 

Could you post your code, then to have a look at it?

Its the same as jfaerman but with the changes you told him to make.



package testes;

import com.jme.app.SimpleGame;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.LightState;

public class Test extends SimpleGame {
   Node sun = new Node( "Sun" );
   Node planet = new Node( "Planet" );
   float angle;
   float initialAngle;
   boolean isRotating;
   
   private void startRotation() {
                System.out.println("Sun "+sun.getLocalTranslation());
                System.out.println("Plnaet "+planet.getLocalTranslation());
      rootNode.detachChild(planet);
      sun.attachChild(planet);
      planet.setLocalRotation( new Quaternion() );
                planet.setLocalTranslation( -50f , 0, 0); // <--- if I comment this line it stops woking....
               
   }
   
   private void endRotation() {
      rootNode.updateGeometricState( 0f, true );
      
      sun.detachChild(planet);
      rootNode.attachChild(planet);
               
                planet.getLocalTranslation().set(planet.getWorldTranslation());
      planet.getLocalRotation().set(planet.getWorldRotation());
   }
   
    @Override
    protected void simpleUpdate() {
       if(KeyBindingManager.getKeyBindingManager().isValidCommand("u", false)){
          isRotating = true;
          initialAngle = angle;
          startRotation();
       }
       else if(isRotating){
          angle += tpf * FastMath.HALF_PI;
          sun.getLocalRotation().fromAngleAxis(-angle, Vector3f.UNIT_Y);          
          if(angle - initialAngle > FastMath.HALF_PI){
             isRotating = false;
             endRotation();
          }
       }
       System.out.println( planet.getLocalTranslation() + " --- " + (planet.getLocalTranslation() == planet.getWorldTranslation()) );
    }

   @Override
   protected void simpleInitGame() {
      cam.getLocation().set(0,20,150);   
      
      sun.attachChild(new Box("box", Vector3f.ZERO, 10, 10, 10));
      rootNode.attachChild(sun);      
      
               
      planet.attachChild(new Box("box", Vector3f.ZERO, 10, 10, 10));
      planet.getLocalTranslation().setX(-50);
      
      rootNode.attachChild(planet);
      rootNode.attachChild(sun);
      
      KeyBindingManager.getKeyBindingManager().add("u", KeyInput.KEY_U);       
   }
   
   public static void main(String[] args)
    {
        Test app = new Test();
        app.setDialogBehaviour(SimpleGame.NEVER_SHOW_PROPS_DIALOG);
        app.start();
    }
}

The answer is simple… Whenever a rotation event finishes, it attaches the planet to the root node, so any information about the translation and rotation must be stored in its local coordinate system since the reference from the sun is lost. This is why you have to do the following:


planet.getLocalTranslation().set(planet.getWorldTranslation());
planet.getLocalRotation().set(planet.getWorldRotation());



in the endRotation method. Like-wise, when the animation starts, you have to change the local coordinate system to that of the sun. Therefore setting the local translation to the fixed value and no rotation, this will make sure the sun is the one dictating the rotations/translations and no dangling data for when it was attached to the root.

Thanks for the explanation, but isnt there a way to do it without constant values, am I able to get the planet.setLocalTranslation( -50f, 0, 0); from some place instead of using a constant field ?

Well, to begin with, this was a very simple and specific example, IMHO you should always try to take advantage of the Scenegraph structure so you would ideally not have to detach and reattach children unless they truly change their reference point (like a character riding a car).

What I am trying to do is apply this solution to my problem, that is explained in this post http://www.jmonkeyengine.com/jmeforum/index.php?topic=8427.0 (in the last post there are some pics explaining my problem)