Attach child with rotated parent to new parent keeping global position

Hi,

how do I attach a node to a different parent without any visible changes in the scene?



I need to do that to use different rotation pivots. Press a key makes the nodes rotate around pivot 1, press another key makes them rotate around pivot 2, etc… but currently the node is moved when I attach to the new parent. I suppose that’s because the coordinate system of the new pivot is at a different position, so although the nodes keep the same local coordinates they end at a different position in the scene.

And right now I don’t know how to solve that.



Thanks in advance

[java]Vector3f newLocalTranslation = newParent.worldToLocal(spatial.getWorldTranslation(), null);

spatial.setLocalTranslation(newLocalTranslation);

newParent.attachChild(spatial);[/java] hope thats right

Hi. Thanks for your answer, but it didnt help.



I investigated more the issue, the problem is this:

The parent where the children are is rotated.

When the children are attached to the second parent, they should keep exactly the same position they have, including the position change due to the parent’s rotation, which they don’t.



getWorldTranslation() and getWorldRotation() (of child) doesn’t seem to reflect this change (I get the same values before and after the rotation of the parent).



Someone could suggest to give the target the same rotation of the current parent and attach - that works. But I have to give it the previous location back, because it has other children which don’t have to be rotated. And if I rotate back then the new child is moved again… same problem.

Here is an edited code of helloworld tutorial with a simplified version of my problem, and some despaired attempts to solve…



[java]/*

  • Copyright © 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 jme3test.helloworld;



    import com.jme3.app.SimpleApplication;

    import com.jme3.input.controls.ActionListener;

    import com.jme3.input.controls.KeyTrigger;

    import com.jme3.material.Material;

    import com.jme3.math.Vector3f;

    import com.jme3.scene.Geometry;

    import com.jme3.scene.shape.Box;

    import com.jme3.math.ColorRGBA;

    import com.jme3.math.Matrix3f;

    import com.jme3.math.Quaternion;

    import com.jme3.scene.Node;



    /
    * Sample 2 - How to use nodes as handles to manipulate objects in the scene.
  • You can rotate, translate, and scale objects by manipulating their parent nodes.
  • The Root Node is special: Only what is attached to the Root Node appears in the scene. /

    public class HelloNode extends SimpleApplication {



    Node pivot1;

    Node pivot2;



    Geometry blue;

    Geometry red;



    public static void main(String[] args){

    HelloNode app = new HelloNode();

    app.start();

    }



    @Override

    public void simpleInitApp() {



    /
    * create a blue box at coordinates (1,-1,1) /

    Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);

    blue = new Geometry("Box1", box1);

    Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");

    mat1.setColor("m_Color", ColorRGBA.Blue);

    blue.setMaterial(mat1);



    /
    * create a red box straight above the blue one at (1,3,1) /

    Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);

    red = new Geometry("Box2", box2);

    Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");

    mat2.setColor("m_Color", ColorRGBA.Red);

    red.setMaterial(mat2);



    /
    * create a red box straight above the blue one at (1,3,1) /

    Box box3 = new Box( new Vector3f(0,2,0), .5f,.5f,.5f);

    Geometry yel = new Geometry("Box3", box3);

    Material mat3 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");

    mat3.setColor("m_Color", ColorRGBA.Yellow);

    yel.setMaterial(mat3);



    /
    * Create a pivot node at (0,0,0) and attach it to the root node /

    pivot1 = new Node("pivot1");

    pivot2 = new Node("pivot2");

    rootNode.attachChild(pivot1);

    rootNode.attachChild(pivot2);



    /
    * Attach the two boxes to the pivot node. (And transitively to the root node.) /

    pivot1.attachChild(blue);

    pivot1.attachChild(red);



    pivot2.attachChild(yel);

    // System.out.println("world trans before rotating:" + red.getWorldTranslation());

    // System.out.println("world rot before rotating:" + red.getWorldRotation());



    /
    * Rotate the pivot node: Note that both boxes have rotated! */

    pivot1.rotate(0.5f, 0.5f, 0f);

    // System.out.println("world trans after rotating:" + red.getWorldTranslation());

    // System.out.println("world rot before rotating:" + red.getWorldRotation());



    inputManager.addMapping("attach_to2", new KeyTrigger(keyInput.KEY_I));



    inputManager.addListener(actionListener, new String[]{"attach_to2"});

    }



    private ActionListener actionListener = new ActionListener() {

    public void onAction(String name, boolean keyPressed, float tpf) {

    if (name.equals("attach_to2") && !keyPressed) {

    // Transform prev = pivot2.getLocalTransform();

    //// pivot2.setLocalTransform(pivot1.getLocalTransform());

    System.out.println("loc rotation of p1:" + pivot1.getLocalRotation());

    Quaternion quat = pivot2.getLocalRotation().clone();

    System.out.println("current roration of pivot2:" + quat);

    pivot2.setLocalRotation(pivot1.getLocalRotation());

    System.out.println("rotation of pivot1:" + pivot1.getLocalRotation());

    pivot2.attachChild(red);

    Vector3f worldTrans = red.getWorldTranslation().clone();

    Quaternion worldRot = red.getWorldRotation(); //?



    // System.out.println("rotation before set the rotation back:" + pivot2.getLocalRotation());

    pivot2.setLocalRotation(quat);

    // System.out.println("set the rotation back:" + quat);

    Vector3f newLocalTranslation = pivot2.worldToLocal(worldTrans, null);

    red.setLocalTranslation(newLocalTranslation);

    // pivot2.setLocalTransform(prev);

    }

    }

    };

    }[/java]

The basic sequence goes like this:


  1. Get the world location for the child using getWorldTranslation().
  2. Get the local translation for the new parent using parent2.worldToLocal() with the world location retrieved in step 1.
  3. Attach the spatial to parent 2.
  4. Set the local translation to what was calculated in step 2.

That is what wezrule said and I tried first, but it doesn’t work



[java] private ActionListener actionListener = new ActionListener() {

public void onAction(String name, boolean keyPressed, float tpf) {

if (name.equals(“attach_to2”) && !keyPressed) {

Vector3f newLocalTranslation = pivot2.worldToLocal(red.getWorldTranslation(), null);

pivot2.attachChild(red);

red.setLocalTranslation(newLocalTranslation);

}

}

};[/java]



(The rest of the code is the same as before)



I think it’s related to the child being at a different position because the parent is rotated and not a direct translation. Correct me if I’m wrong.

@ivanschuetz said:
That is what wezrule said and I tried first, but it doesn't work

[java] private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("attach_to2") && !keyPressed) {
Vector3f newLocalTranslation = pivot2.worldToLocal(red.getWorldTranslation(), null);
pivot2.attachChild(red);
red.setLocalTranslation(newLocalTranslation);
}
}
};[/java]

(The rest of the code is the same as before)

I think it's related to the child being at a different position because the parent is rotated and not a direct translation. Correct me if I'm wrong.


getWorldTranslation() and worldToLocal() should take the parent's rotation into consideration. So I'm not sure what's wrong.

Edit: The problem described in this post has been solved by updating to the currend SDK (but the initial issue persists).



I’m getting something strange now. A simple output of child.getWorldTranslation() before rotating the parent makes that the child doesn’t rotate o.O

If I comment out that line it rotates.

Look

[java]public void simpleInitApp() {



/** create a blue box at coordinates (1,-1,1) /

Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);

blue = new Geometry(“Box1”, box1);

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

mat1.setColor(“m_Color”, ColorRGBA.Blue);

blue.setMaterial(mat1);



/
* create a red box straight above the blue one at (1,3,1) /

Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);

red = new Geometry(“Box2”, box2);

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

mat2.setColor(“m_Color”, ColorRGBA.Red);

red.setMaterial(mat2);



/
* create a red box straight above the blue one at (1,3,1) /

Box box3 = new Box( new Vector3f(0,2,0), .5f,.5f,.5f);

Geometry yel = new Geometry(“Box3”, box3);

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

mat3.setColor(“m_Color”, ColorRGBA.Yellow);

yel.setMaterial(mat3);



/
* Create a pivot node at (0,0,0) and attach it to the root node /

pivot1 = new Node(“pivot1”);

pivot2 = new Node(“pivot2”);

rootNode.attachChild(pivot1);

rootNode.attachChild(pivot2);



/
* Attach the two boxes to the pivot node. (And transitively to the root node.) /

pivot1.attachChild(blue);

pivot1.attachChild(red);



pivot2.attachChild(yel);



//this line makes red doesn’t rotate when rotating pivot1

System.out.println(“world trans before rotating:” + red.getWorldTranslation());



/
* Rotate the pivot node: Note that both boxes have rotated! */

pivot1.rotate(0.5f, 0.5f, 0f);



inputManager.addMapping(“attach_to2”, new KeyTrigger(keyInput.KEY_I));



inputManager.addListener(actionListener, new String[]{“attach_to2”});

}[/java]



Maybe this is because I’m using a very old version of the JME Platform, will try to update or install again.

@pspeed if getWorldTranslation() takes parent’s rotation in consideration why I get the same output before and after rotating the parent?

[java]System.out.println(“world trans before rotating:” + red.getWorldTranslation());



pivot1.rotate(0.5f, 0.5f, 0f);

System.out.println(“world trans after rotating:” + red.getWorldTranslation());[/java]



world trans before rotating:(0.0, 0.0, 0.0)

world trans after rotating:(0.0, 0.0, 0.0)



Here is the complete code again without trash and comments if somebody wants to test

[java]import com.jme3.app.SimpleApplication;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.material.Material;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.shape.Box;

import com.jme3.math.ColorRGBA;

import com.jme3.scene.Node;



public class Main extends SimpleApplication {



Node pivot1;

Node pivot2;



Geometry blue;

Geometry red;



public static void main(String[] args){

Main app = new Main();

app.start();

}



@Override

public void simpleInitApp() {

Box box1 = new Box( new Vector3f(1,-1,1), 1,1,1);

blue = new Geometry(“Box1”, box1);

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

mat1.setColor(“m_Color”, ColorRGBA.Blue);

blue.setMaterial(mat1);



Box box2 = new Box( new Vector3f(1,3,1), 1,1,1);

red = new Geometry(“Box2”, box2);

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

mat2.setColor(“m_Color”, ColorRGBA.Red);

red.setMaterial(mat2);



Box box3 = new Box( new Vector3f(0,2,0), .5f,.5f,.5f);

Geometry yel = new Geometry(“Box3”, box3);

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

mat3.setColor(“m_Color”, ColorRGBA.Yellow);

yel.setMaterial(mat3);



pivot1 = new Node(“pivot1”);

pivot2 = new Node(“pivot2”);

rootNode.attachChild(pivot1);

rootNode.attachChild(pivot2);



pivot1.attachChild(blue);

pivot1.attachChild(red);



pivot2.attachChild(yel);

System.out.println(“world trans before rotating:” + red.getWorldTranslation());



pivot1.rotate(0.5f, 0.5f, 0f);

System.out.println(“world trans after rotating:” + red.getWorldTranslation());



inputManager.addMapping(“attach_to2”, new KeyTrigger(keyInput.KEY_I));



inputManager.addListener(actionListener, new String[]{“attach_to2”});

}



private ActionListener actionListener = new ActionListener() {

public void onAction(String name, boolean keyPressed, float tpf) {

if (name.equals(“attach_to2”) && !keyPressed) {

Vector3f newLocalTranslation = pivot2.worldToLocal(red.getWorldTranslation(), null);

pivot2.attachChild(red);

red.setLocalTranslation(newLocalTranslation);

}

}

};

}[/java]

Because 0,0,0 rotated is still 0,0,0.

But the box is at 1,3,1 (local coords) and when I rotate the parent I see a position change.



And anyways I’m getting worldTranslation 0,0,0 for every node in the scene no matter when and where they are.

No, you are outputting the position of the Geometry that contains the box that is positioned. You can’t get the position of the “mesh” because there really isn’t a such thing.



You can put triangles wherever you want in a mesh. It has nothing to do with where the Geometry is. For example, I could make a mesh with a triangle at -10000,0,0 and another at 10000,0,0… that will be relative to where the Geometry is.



You are better off in this case leaving your Boxes at 0,0,0 and moving the Geometry instead.

That’s right! I put the boxes on 0,0,0 and set local translation to the geometries and it works.



The only problem I still have is the rotation of the child itself. When I attach it to the second parent it’s set back to 0, 0, 0



And I don’t find a method to set the rotation like the one we used for the translation.

Rotation doesn’t require the same level of local to world, world to local that translation does. Translation requires that because the accumulated translations, rotations, scales, etc. all affect it. Rotation is a much simpler stack.



I’m working off the top of my head, so forgive me if I get some details wrong…



Give parent p1 with some world rotation pQ1 and a child with its own world rotation cQ. If you want to add it to parent p2 with some world rotation pQ2 such that the resulting world rotation is the same then it’s something like:



Q = cQ * pQ1.inverse * pQ2



I’ve short-handed it and hopefully I’ve got it right. If those are Quaternions then the * is a mult. It’s also possible I have the order reversed. Premultiply/postmultiply always screws me up.

Hey, thanks a lot!

I got it working!



with Q = pQ2.inverse * cQ



I let out pQ1.inverse because it’s already included in the world rotation of the child, and used pQ2.inverse, because that’s what has to be “subtracted” from the rotation of the child after attaching it to the new parent.



Well, it works :slight_smile: Thanks again.

Well, I’m glad you got it working… but it’s still strange.



The point of the pQ1.inverse was because that is already included in the world rotation of the child. But you are right that my example was wrong because it included pQ2 at all… which would automatically be included once the child was added to p2.



So in theory:

Q = cQ * pQ1.inverse

on its own should work for the new child rotation when attaching it to p2… but I haven’t tried it.