attachChild on the fly and setting local translation

I have balls that roll around and run into each other. When they hit, they should stick together (one becomes the child of the other). I attachChild() and set the child’s local translation (to 3f, 0f, 0f for example) but the world translations go way off - by hundreds. I also get a change in scale which is not reflected in the output (crazy, I know). Is it possible to upload a zip file?

//sample output
collision (Ant#2 and Ant#3)
nodeList[0]=FormNode [Ant#3, #3, 3, x=0.0, y=0.0, z=3.0, color=Color[0.2677251, 0.3141973, 0.19586271, 1.0]]
set to =0.0, 0.0, 3.0
child world trans before=(3.549219, 0.099999994, 0.64092755)
child local trans before=(3.549219, 0.099999994, 0.64092755)
Ant#2.attachChild(Ant#3) code is 6
child local trans after=(0.0, 0.0, 3.0)
parent world trans after=(3.7876701, 0.099999994, 0.6678786)
child world trans after=(3.7876701, 0.099999994, -186.47888)
child local scale after=(10.0, 10.0, 10.0)
//children
Models/Ant/AntSphere.blend, (0.0, 0.0, 0.0)
Processor, (0.0, 0.0, 0.0)
Battery, (0.0, 0.0, 0.0)
GPS, (0.0, 0.0, 0.0)
Radio, (0.0, 0.0, 0.0)
Ant#3, (0.0, 0.0, 3.0)

Here is my test case. It works great. :smiley:
But, in program, it doesn’t work. I’m thinking it may be a relationship between the model and the node that holds it but, it looks ok to me.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
//animation
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Sphere;
import java.awt.Frame;
import javax.swing.JFrame;


/**
 * test
 * @author normenhansen
 */
public class Main extends SimpleApplication{
    private AnimChannel channel;
    private AnimControl control;
    //Node wedge;
    //Color skyblue = new Colorf(.5f, .5f, .5f);
    static JFrame window;
    public static void main(String[] args) {
        Main app = new Main();
        
        app.start();
    }

    @Override
    public void simpleInitApp() {
        //super();
        System.out.println("simpleInitApp()");
        Node[] ball = new Node[3];
        
        flyCam.setMoveSpeed(50);
        viewPort.setBackgroundColor(ColorRGBA.LightGray);
        
        DirectionalLight dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalize());
        rootNode.addLight(dl);
        //scene objects
        for (int i = 0;i < ball.length;i++){
            ball[i] = new Node("ball" + i);
            Sphere sphere = new Sphere(20, 20, 1);
            Geometry geo = new Geometry("sphere" + i, sphere);
            Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
            geo.setMaterial(mat);
            mat.setColor("Color", ColorRGBA.randomColor());
            ball[i].attachChild(geo);
            //ball[i] = new Node();
            

            if (i == 0){
                //leave at 0, 0, 0 and 0, 0, 0
                ball[i].setUserData("waiting", new Boolean(true));
                ball[i].setUserData("walking", new Boolean(false));
                ball[i].addControl(new RollControl(ball));
                rootNode.attachChild(ball[i]);
            }
            else if (i == 1){
                ball[i].setLocalTranslation(2f, 0f, 0f);
                ball[0].attachChild(ball[i]);
                ball[i].setUserData("waiting", new Boolean(false));
                ball[i].setUserData("walking", new Boolean(false));
                ball[i].addControl(new RollControl(ball));
            }
            else if (i == 2){
                ball[i].setUserData("waiting", new Boolean(false));
                ball[i].setUserData("walking", new Boolean(true));
                ball[i].setLocalTranslation(-5f, 0f, 0f);
                ball[i].addControl(new RollControl(ball));
                rootNode.attachChild(ball[i]);
            }
            
        }

        initKeys();
    }
    
    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName){
        if (animName.equals("Walk")){
            channel.setAnim("stand", 0.50f);
            channel.setLoopMode(LoopMode.DontLoop);
            channel.setSpeed(1f);
           
        }
    }
    
    public void onAnimChange(AnimControl control, AnimChannel channel, String animName){
        //unused
    }
    
    
    private void initKeys(){
        inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addListener(actionListener, "Walk");
    }
    
    private ActionListener actionListener = new ActionListener(){
        public void onAction(String name, boolean keyPressed, float tpf){
            if(name.equals("Walk") && !keyPressed){
                if(!channel.getAnimationName().equals("Walk")){
                    channel.setAnim("Walk", 0.50f);
                    channel.setLoopMode(LoopMode.Loop);
                }
            }
        }
    };

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package mygame;
//http://vormplus.be/blog/article/drawing-a-cylinder-with-processing

//import com.jme3.scene.Geometry;
//import com.jme3.material.Material;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.util.BufferUtils;
import java.util.List;
import java.io.IOException;

//cylinder
/**
 *
 * @author Peter
 */
//0 front, 1 back, 2 right, left 3, top, 4, bottom 5, 
//body 0, cap 1
//body 0, top 1, bottom 2
public class RollControl extends AbstractControl{
    float speed = 1;
    Node[] ball = new Node[3];
    private final Quaternion lookRotation = new Quaternion();
    //}
    
    public RollControl(){
    }
    
    public RollControl(Node[] ball){
        this.ball = ball;
    }
    
    @Override
    protected void controlUpdate(float tpf){
        if ((Boolean)spatial.getUserData("walking")){
            Vector3f aim = ball[0].getWorldTranslation();
            Vector3f dist = aim.subtract(spatial.getWorldTranslation());//my location

            if (dist.length() < 2) {
                spatial.setUserData("walking", new Boolean(false));
            } else {
                dist.normalizeLocal();
                lookRotation.lookAt(dist, Vector3f.UNIT_Y);
                spatial.setLocalRotation(lookRotation);
                spatial.move(dist.multLocal(speed * tpf));
            }
        }
        if ((Boolean)spatial.getUserData("waiting")){
            Vector3f aim = ball[2].getWorldTranslation();
            Vector3f dist = aim.subtract(spatial.getWorldTranslation());//my location

            if (dist.length() < 2) {
                spatial.setUserData("waiting", new Boolean(false));
                System.out.println("ready!");
                ball[0].attachChild(ball[2]);
                ball[2].setLocalTranslation(-2f, 0f, 0f);
                List list = ball[0].getChildren();
                for (int i = 0;i < list.size();i++){
                    Spatial thing = (Spatial)list.get(i);
                    System.out.println(thing.getName());
                }
                ball[2].setUserData("walking", new Boolean(false));
                spatial.setUserData("walking", new Boolean(true));
            } else {
                //just wait
            }
        }
    }

    protected void setSpeed(float speed){
        this.speed = speed;
    }
    
    
    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        //Only needed for rendering-related operations,
        //not called when spatial is culled.
    }
    
    public Control cloneForSpatial(Spatial spatial) {
        RollControl control = new RollControl();
        //TODO: copy parameters to new Control
        return control;
    }
    
    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule in = im.getCapsule(this);
        //TODO: load properties of this Control, e.g.
        //this.value = in.readFloat("name", defaultValue);
    }
    
    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule out = ex.getCapsule(this);
        //TODO: save properties of this Control, e.g.
        //out.write(this.value, "name", defaultValue);
    }



}
    //parent
    public boolean connectToTarget(float tpf){
    for (int a = 0;a < targetList.length;a++){//for each string/join
        if (!nodeList[a].getName().isEmpty()){//ingore ends with no links
            //check dist
            Vector3f aim = targetList[a].getWorldTranslation();//other location - sent from loc request
            Vector3f dist = aim.subtract(spatial.getWorldTranslation());//my location
            //check distance of nodes moving to you
            if (dist.length() < (0.25)) {
                System.out.println("collision (" + ant.getName() + " and " + targetList[a].getName() + ")");
                //find who hit you
                Node node = spatial.getParent();
                if (node != null) {
                    final List<Spatial> ants = new LinkedList<Spatial>();
                    node.depthFirstTraversal(new SceneGraphVisitor() {
                        @Override
                        public void visit(Spatial spatial) {
                            ants.add(spatial);
                        }
                    });
                    //repare to attachChild
                    Ant tempParent = ant;//me
                    //search target's name
                    Ant tempChild = null;
                    for (int b = 0;b < ants.size();b++){
                        if (ants.get(b).getName().equals(targetList[a].getName())) {
                            tempChild = (Ant)ants.get(b);
                        }
                    }
                    //checking shortcut to get child
                    tempChild = targetList[a];
                    //got both - parent and child
                    if (tempParent != null && tempChild != null){
                        //print attach data
                        System.out.println("nodeList[" + a + "]=" + nodeList[a]);
                        System.out.println("set to =" + nodeList[a].getX() + ", " + nodeList[a].getY() + ", " + nodeList[a].getZ());
                        System.out.println("child  world trans before=" + tempChild.getWorldTranslation());
                        System.out.println("child  local trans before=" + tempChild.getLocalTranslation());
                        //attach and reset local trans
                        int h = tempParent.attachChild(tempChild);
                        tempChild.setLocalTranslation(nodeList[a].getX(), nodeList[a].getY(), nodeList[a].getZ());
                        //check attachment (returns num of obj in list)
                        System.out.println(tempParent.getName() + ".attachChild(" + tempChild.getName() + ") code is " + h);
                        //check local trans
                        System.out.println("child  local trans after=" + tempChild.getLocalTranslation());
                        System.out.println("parent world trans after=" + tempParent.getWorldTranslation());
                        System.out.println("child  world trans after=" + tempChild.getWorldTranslation());
                        System.out.println("child  local scale after=" + tempChild.getLocalScale());
                        //check attachement
                        List list = ant.getChildren();
                        for (int i = 0;i < list.size();i++){
                            Spatial thing = (Spatial)list.get(i);
                            System.out.println(thing.getName() + ", " + thing.getLocalTranslation());
                        }

                        tempParent.setWalking(false);
                        tempParent.setMoving(false);
                        tempParent.setStanding(true);
                        //turn everything off
                        tempChild.setInvited(false);
                        tempChild.setStanding(false);
                        tempChild.setMoving(false);
                        tempChild.setWalking(false);
                        tempChild.setGrouped(true);

                        System.out.println();

                        spatial.move(0f, 0f, 0f);//maybe this will cause a recalc of childs LT (nope!)
                    }
                    else{
                        //if (ant.getID() == 0 || ant.getID() == 1) System.out.println(tempParent.getName() + ".attachChild(" + tempChild.getName() + ") code is " + h);

                    }
                }//null node
            }//dist
        }//is empty
            //}
        }//not grouped
        return(true);
    }

I don’t think you’ve told us anything about the scale of the parent… or most anything about it really. I’m assuming that you understand how a scene graph works and that once a spatial becomes a child of another node that it then assumes all of the translation, rotation, scale of that parent?

And posting code that works doesn’t really help us determine why the code we can’t see doesn’t work… though I’ll admit it’s a new variation on the “how long is this piece of string I’m holding” style questions. :smile:

All the balls are attached to the root node and when they bump, one, marked at the ‘lessor’ gets attached to the ‘greater’. It’s loosely based on Quixote WalkControl. When things get close enough, something happens.
The third code block is the code that makes the output. The LocalTransformation of the attached object is 0 0 3, for example, so it should be right next to the other object but its WorldLocation is hundreds off. The scale for the balls is 10f, 10f 10f so the balls should be close together.
The ‘child world trans after’ should be ‘parent world trans after’ plus 0, 0, 3. At best, it should me half merged with the first ball.
I can’t see posting all 20 classes so someone can look at them and see their interactions. is there a way to upload a zip file so someone can run it?

When you add a child to a node with scale 10, 10, 10 what do you suppose happens to its local translation in world space?

It’s not about posting your whole code. It’s about making a test case that actually illustrates the problem. Else the code posted isn’t useful for diagnosing the problem any more than a beef stew recipe would be.

Anyway, while I hate to guess at a question… perhaps this is useful to you:
http://javadoc.jmonkeyengine.org/com/jme3/scene/Spatial.html#worldToLocal(com.jme3.math.Vector3f,%20com.jme3.math.Vector3f)

This might also be helpful:
http://wiki.jmonkeyengine.org/doku.php/jme3:scenegraph_for_dummies

OOps. My bad. I made my size 0.1 or 0.01 and blew up the balls so i could see them better by scaling them. I also used whole numbers when I attached them together and setLocalTranslations instead of fractions/decimals.
I watched the scenegraph for dummies a couple of times before (as well as having done 3D for many years) and missed the depth of the scaling and local translation. It began to dawn on me the second time i watched it after you suggested it.
Anyway, thanks for your time.