How to read userdata in the constructor of the attached control?

How to read user data in the constructor of a control? For example, in the following code, I am trying to read an user data in the constructor but its throwing NullPointer exception.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.showSettings = false;
        app.start();
    }

    @Override
    public void simpleInitApp() {

        Geometry geom = new Geometry("Box", new Box(1f, 1f, 1f));
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.randomColor());
        geom.setMaterial(mat);
        geom.setUserData("name", "BoxGeom");    // setting user data
        geom.addControl(new GeomControl());     // trying to read the user data at the control's constructor 
        rootNode.attachChild(geom);
    }

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

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

class GeomControl extends AbstractControl{
    
    public GeomControl() {
        System.out.println(spatial.getUserData("name")); // throws NullPointerException
    }

    @Override
    protected void controlUpdate(float tpf) {
        
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }
    
}

That’s because in the constructor the spatial variable hasn’t initialized yet. Try overriding the setSpatial(Spatial) method and calling spatial.getUserData from there. That should work.

Do you mean something like this?

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.showSettings = false;
        app.start();
    }

    @Override
    public void simpleInitApp() {

        Geometry geom = new Geometry("Box", new Box(1f, 1f, 1f));
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.randomColor());
        geom.setMaterial(mat);
        geom.setUserData("name", "BoxGeom");    // setting user data
        
        GeomControl g = new GeomControl();
        g.setSpatial(geom);
        geom.addControl(g);   
        
        rootNode.attachChild(geom);
    }

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

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

class GeomControl extends AbstractControl{
    
    public GeomControl() {
//        System.out.println(spatial);  // prints 'null'
    }

    @Override
    public void setSpatial(Spatial spatial) {
        this.spatial = spatial;
        System.out.println(spatial.getUserData("name"));
    }

    
    @Override
    protected void controlUpdate(float tpf) {
        
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }
    
}

Safer to do:

@Override
public void setSpatial( Spatial spatial ) {
    super.setSpatial(spatial);
    System.out.println(spatial.getUserData("name"));
}

…because the super class might do more on setSpatial() than you expect and by overriding it you might mess it up.

But yes, that’s what he meant. You can’t do it in the constructor because the control hasn’t even been added to a spatial yet at that point.

2 Likes

Also, that’s redundant. Do NOT setSpatial() on a control yourself. addControl() will do it and often controls will throw an exception if you try to do it twice (as it’s 99% of the time a sign of a bug or bad code like the above).

2 Likes

It’s not that some “magic” is preventing you to read data in constructor, it’s that constructor is called before adding control to any geometry. I’d recommend you to learn basics of java and OOP before trying to move to more complex things like JME3.

Thanks that solved it.