Creating lemur components using 3D nodes

I’m trying to make a skin/model choosing dialog for the player.
A sketch of what I’m thinking:
Imgur
What I’m getting:
Imgur
Not everything (Ex owned/cost) is implemented, you probably get the general idea.

My Entire MainMenu class (ABSOLUTE MESS)
package com.indigoa.game.crushed.state;

import com.indigoa.game.crushed.GameLogic;
import com.indigoa.game.crushed.Main;
import static com.indigoa.game.crushed.Main.gl;
import com.indigoa.game.crushed.UsernameGenerator;
import com.indigoa.game.crushed.Variables;
import com.indigoa.game.crushed.skin.PlayerSkin;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Command;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.IconComponent;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.core.GuiControl;
import com.simsilica.lemur.event.MouseListener;
import com.simsilica.lemur.style.Attributes;
import com.simsilica.lemur.style.BaseStyles;
import java.awt.event.MouseEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.MouseInputListener;

/**
 *
 * @author mccra
 */
public class MainMenu extends AbstractAppState {
    Geometry title, darken;
    Spatial coinMDL;
    Container lemur, smenu, name, sm;
    Button play, coins;
    float ttpScale = 1;
    boolean ttpDir = false, triggered = false, settingsOpen = false;
    TextField nameBlank;
    
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        Main main = (Main) app;
        title = new Geometry("title", new Quad(Main.settings_.getWidth() / 2, (Main.settings_.getWidth() / 2) / 7));
        Material mat = new Material(Main.assets, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setTexture("ColorMap", Main.assets.loadTexture("Textures/Title.png"));
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        title.setMaterial(mat);
        title.setLocalTranslation((Main.settings_.getWidth() / 4), (Main.settings_.getHeight() / 2) + (Main.settings_.getHeight() / 5), 0);
        Main.guiNode_.attachChild(title);
        
        darken = new Geometry("darken", new Quad(Main.settings_.getWidth(), Main.settings_.getHeight()));
        mat = new Material(Main.assets, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f));
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        darken.setMaterial(mat);
        darken.setLocalTranslation(0, 0, -1);
        Main.guiNode_.attachChild(darken);
        gl.player.killed = 0;
        gl.destroy();
        gl.genCrushers(10);
        gl.genNPCs(20);
        gl.setupPlayer();
        gl.attatchChildrenToNode(Main.rootNode_);
        //gl.update();
        
        
        GuiGlobals.initialize(Main.instance);
        //GuiGlobals.getInstance().setCursorEventsEnabled(true, true);
        QuadBackgroundComponent bg;
        Attributes a;
        a = GuiGlobals.getInstance().getStyles().getSelector(TextField.ELEMENT_ID, GuiGlobals.getInstance().getStyles().getDefaultStyle());
        bg = new QuadBackgroundComponent(new ColorRGBA(0, 0, 0, 0.5f));
        a.set("background", bg);
        
        play = new Button("");
        IconComponent icon = new IconComponent("Textures/start.png");
        icon.setIconSize(new Vector2f(Main.settings_.getWidth() / 6, Main.settings_.getWidth() / 6));
        play.setIcon(icon);
        play.setLocalTranslation(Main.settings_.getWidth() / 3 + Main.settings_.getWidth() / 12, Main.settings_.getHeight() / 3, 0);
        play.addClickCommands((Button source) -> {
            if (triggered) return;
            triggered = true;
            Running.killAll = false;
            Main.stateManager_.attach(new Running());
            Main.stateManager_.detach(MainMenu.this);
        });
        Main.guiNode_.attachChild(play);
        
        //GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
        lemur = new Container();
        Main.guiNode_.attachChild(lemur);
        lemur.setLocalTranslation(0, Main.settings_.getHeight(), 0);
        Button settings = lemur.addChild(new Button(""));
        icon = new IconComponent("Textures/gear.png");
        icon.setIconSize(new Vector2f(Main.settings_.getWidth() / 15, Main.settings_.getWidth() / 15));
        settings.setIcon(icon);
        if (Variables.instance.hasAds()) {
            Button ads = lemur.addChild(new Button(""));
            icon = new IconComponent("Textures/noads.png");
            icon.setIconSize(new Vector2f(Main.settings_.getWidth() / 15, Main.settings_.getWidth() / 15));
            ads.setIcon(icon);
            ads.addClickCommands((Button source) -> {
                Variables.instance.buyNoAds();
            });
        }
        
        //GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
        smenu = new Container();
        //Main.guiNode_.attachChild(smenu);
        smenu.setLocalTranslation(0, Main.settings_.getHeight() - Main.settings_.getWidth() / 15, 0);
        
        Button lgm = smenu.addChild(new Button((Variables.instance.fastmode() ? "High" : "Low") + " Graphics Mode"));
        lgm.setFontSize(Main.settings_.getWidth() / 32);
        lgm.addClickCommands((Button source) -> {
            if (Variables.instance.fastmode()) {
                Variables.instance.setFastmode(false);
                Main.instance.reload();
            } else {
                Variables.instance.setFastmode(true);
                Main.instance.reload();
            }
            lgm.setText((Variables.instance.fastmode() ? "High" : "Low") + " Graphics Mode");
        });
        settings.addClickCommands((Button source) -> {
            System.out.println("settingsOpen: " + settingsOpen);
            if (settingsOpen) {
                settingsOpen = false;
                Main.guiNode_.detachChild(smenu);
            } else {
                settingsOpen = true;
                Main.guiNode_.attachChild(smenu);
            }
        });
        Button reload = smenu.addChild(new Button("Quick Reload"));
        reload.setFontSize(Main.settings_.getWidth() / 32);
        reload.addClickCommands((Button source) -> {
            Main.instance.reload();
        });
        
        coins = new Button(String.valueOf(Variables.instance.getCoins()));
        coins.setFontSize(Main.settings_.getWidth() / 32);
        coinMDL = Main.assets.loadModel("Models/coin_tri.j3o");
        coinMDL.addLight(new DirectionalLight(new Vector3f(-0.6f, -0.6f, -0.6f)));
        DirectionalLight light = new DirectionalLight(new Vector3f(0.1f, -1f, -0.2f));
            //light = new AmbientLight();
            coinMDL.addLight(light);
            coinMDL.addLight(new DirectionalLight(new Vector3f(0.1f, -1f, -0.45f)));
            coinMDL.addLight(new DirectionalLight(new Vector3f(0.5f, -0.5f, 0.45f)));
        coinMDL.setLocalScale(Main.settings_.getWidth() / 30);
        coinMDL.setLocalTranslation(Main.settings_.getWidth() / 30, (Main.settings_.getHeight() / 3) * 2, 0);
        // mat = new Material(Main.assets, "Common/MatDefs/Misc/Unshaded.j3md");
        //    mat.setColor("Color", new ColorRGBA(0.8f, 0.5f, 0, 1));
        //    coinMDL.setMaterial(mat);
        coinMDL.rotate(0, 70 * FastMath.DEG_TO_RAD, 0);
        coins.setLocalTranslation(Main.settings_.getWidth() / 30 * 2, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 30, 0);
        coins.addClickCommands((Button source) -> {
                Variables.instance.buyCoins();
            });
        Main.guiNode_.attachChild(coins);
        Main.guiNode_.attachChild(coinMDL);
        
        name = new Container();
        Main.guiNode_.attachChild(name);
        Label l = new Label("          Username          ");
        l.setFontSize(Main.settings_.getWidth() / 24);
        name.addChild(l);
        
        nameBlank = name.addChild(new TextField(Variables.instance.getUsername()));
        nameBlank.setFontSize(Main.settings_.getWidth() / 24);
        nameBlank.setSingleLine(true);
        nameBlank.addMouseListener(new MouseListener() {
            @Override
            public void mouseButtonEvent(MouseButtonEvent event, Spatial target, Spatial capture) {
                System.out.println(event + "  " + event.getButtonIndex() + "  " + event.isReleased());
                if (event.getButtonIndex() == MouseInput.BUTTON_LEFT && event.isReleased())Variables.instance.openKeyboard(nameBlank, () -> {Variables.instance.setUsername(nameBlank.getText());});
            }

            @Override
            public void mouseEntered(MouseMotionEvent event, Spatial target, Spatial capture) {}

            @Override
            public void mouseExited(MouseMotionEvent event, Spatial target, Spatial capture) {}

            @Override
            public void mouseMoved(MouseMotionEvent event, Spatial target, Spatial capture) {}
        });
        name.setLocalTranslation(0, Main.settings_.getHeight() / 2, 0);
        
        Button skins = name.addChild(new Button("Skins"));
        skins.setFontSize(Main.settings_.getWidth() / 24);
        skins.addClickCommands((Button source) -> {
            skinMenu();
        });
        
        sm = new Container();
        sm.setSize(new Vector3f(Main.settings_.getWidth(), Main.settings_.getHeight(), 0));
        sm.setLocalTranslation(0, Main.settings_.getHeight(), ttpScale);
        
        
        Running.killAll = true;
    }
    
    public void skinMenu() {
        sm.clearChildren();
        //Back button/coins
        Button back = sm.addChild(new Button("Back"), 0, 0);
        back.addClickCommands((Button source) -> {
            closeSkinMenu();
        });
        //Button back = p.addChild(new Button("Back"), 0, 0);
        coins.setLocalTranslation(Main.settings_.getWidth() / 30 * 2, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 6 + Main.settings_.getWidth() / 30, 0);
        coinMDL.setLocalTranslation(Main.settings_.getWidth() / 30, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 6, 0);
        //Current skin
        Node n = Main.gl.player.skin.getGUISkin();
        n.setLocalScale(Main.settings_.getHeight() / 3);
        //n.setLocalTranslation(Main.settings_.getWidth() / 2, Main.settings_.getHeight() / 3, 0);
        n.getControl(GuiControl.class).setSize(new Vector3f(Main.settings_.getHeight() / 3, Main.settings_.getHeight() / 3, 0));
        sm.addChild(n, 1);
        //Options
        Container skins = new Container();
        for (String s : PlayerSkin.skins.keySet()) {
            PlayerSkin skin = PlayerSkin.getSkin(s);
            n = skin.getGUISkin();
            n.setLocalScale(Main.settings_.getHeight() / 8);
            n.getControl(GuiControl.class).setSize(new Vector3f(Main.settings_.getHeight() / 8, Main.settings_.getHeight() / 8, 0));
            skins.addChild(n);
        }
        sm.addChild(skins);
        //Detatch/attatch objects
        Main.guiNode_.detachChild(title);
        Main.guiNode_.detachChild(play);
        //Main.guiNode_.detachChild(darken);
        Main.guiNode_.detachChild(lemur);
        Main.guiNode_.detachChild(smenu);
        Main.guiNode_.detachChild(name);
        //Main.guiNode_.detachChild(coins);
        //Main.guiNode_.detachChild(coinMDL);
        Main.guiNode_.attachChild(sm);
    }
    public void closeSkinMenu() {
        Main.guiNode_.attachChild(title);
        Main.guiNode_.attachChild(play);
        //Main.guiNode_.attachChild(darken);
        Main.guiNode_.attachChild(lemur);
        Main.guiNode_.attachChild(smenu);
        Main.guiNode_.attachChild(name);
        coins.setLocalTranslation(Main.settings_.getWidth() / 30 * 2, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 30, 0);
        //Main.guiNode_.attachChild(coins);
        coinMDL.setLocalTranslation(Main.settings_.getWidth() / 30, (Main.settings_.getHeight() / 3) * 2, 0);
        //Main.guiNode_.attachChild(coinMDL);
        Main.guiNode_.detachChild(sm);
    }
    
    @Override
    public void update(float tpf) {
        coinMDL.rotate(0, 0.02f, 0);
        while (Main.stateManager_.getState(Running.class) != null) {
            Main.stateManager_.detach(Main.stateManager_.getState(Running.class));
        }
        while (Main.stateManager_.getState(Restart.class) != null) {
            Main.stateManager_.detach(Main.stateManager_.getState(Restart.class));
        }
        while (Main.stateManager_.getState(YouWin.class) != null) {
            Main.stateManager_.detach(Main.stateManager_.getState(YouWin.class));
        }
        while (Main.stateManager_.getState(MainMenu.class) != null && Main.stateManager_.getState(MainMenu.class) != this) {
            Main.stateManager_.detach(Main.stateManager_.getState(MainMenu.class));
        }
    }
    
    @Override
    public void cleanup() {
        super.cleanup();
        Main.guiNode_.detachChild(title);
        Main.guiNode_.detachChild(play);
        Main.guiNode_.detachChild(darken);
        Main.guiNode_.detachChild(lemur);
        Main.guiNode_.detachChild(smenu);
        Main.guiNode_.detachChild(name);
        Main.guiNode_.detachChild(coins);
        Main.guiNode_.detachChild(coinMDL);
    }
    
}

This the method I think I need to worry about:

public void skinMenu() {
        sm.clearChildren();
        //Back button/coins
        Button back = sm.addChild(new Button("Back"), 0, 0);
        back.addClickCommands((Button source) -> {
            closeSkinMenu();
        });
        //Button back = p.addChild(new Button("Back"), 0, 0);
        coins.setLocalTranslation(Main.settings_.getWidth() / 30 * 2, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 6 + Main.settings_.getWidth() / 30, 0);
        coinMDL.setLocalTranslation(Main.settings_.getWidth() / 30, (Main.settings_.getHeight() / 3) * 2 + Main.settings_.getWidth() / 6, 0);
        //Current skin
        Node n = Main.gl.player.skin.getGUISkin();
        n.setLocalScale(Main.settings_.getHeight() / 3);
        //n.setLocalTranslation(Main.settings_.getWidth() / 2, Main.settings_.getHeight() / 3, 0);
        n.getControl(GuiControl.class).setSize(new Vector3f(Main.settings_.getHeight() / 3, Main.settings_.getHeight() / 3, 0));
        sm.addChild(n, 1);
        //Options
        Container skins = new Container();
        for (String s : PlayerSkin.skins.keySet()) {
            PlayerSkin skin = PlayerSkin.getSkin(s);
            n = skin.getGUISkin();
            n.setLocalScale(Main.settings_.getHeight() / 8);
            n.getControl(GuiControl.class).setSize(new Vector3f(Main.settings_.getHeight() / 8, Main.settings_.getHeight() / 8, 0));
            skins.addChild(n);
        }
        sm.addChild(skins);
        //Detatch/attatch objects
        Main.guiNode_.detachChild(title);
        Main.guiNode_.detachChild(play);
        //Main.guiNode_.detachChild(darken);
        Main.guiNode_.detachChild(lemur);
        Main.guiNode_.detachChild(smenu);
        Main.guiNode_.detachChild(name);
        //Main.guiNode_.detachChild(coins);
        //Main.guiNode_.detachChild(coinMDL);
        Main.guiNode_.attachChild(sm);
    }

I was thinking that

n.getControl(GuiControl.class).setSize(new Vector3f(Main.settings_.getHeight() / 3, Main.settings_.getHeight() / 3, 0));

Would set the size for the layout. What I want to do Is set the size that the layout thinks my node is, that way additional UI elements will account for the size of the first, and not display on top of each other.

Here is the PlayerSkin.getGUISkin() method:

public Node getGUISkin() {
        Node n = new Node();
        n.addControl(new GuiControl("skin_inventory"));
        Node in = new Node();
        in.attachChild(getBody()); //Abstract, different for each skin
        in.attachChild(getSpear()); //Abstract, different for each skin
        in.setLocalTranslation(0.8f, -0.8f, 0);
        in.rotate(0, 200 * FastMath.DEG_TO_RAD, -15 * FastMath.DEG_TO_RAD);
        n.attachChild(in);
        return n;
    }

Any help would be appreciated, thaks :smiley:

setSize() will set the actual size if the GUI element… in the case of regular GUI elements this will stretch them/shrink them etc. For any child of a layout, this will always be set by the layout.

setPreferredSize() is what tells the layouts what size that object prefers and will be used to calculate the layout and position the other elements. (In Java Swing terms this is like a combination of ‘minimum size’ and ‘preferred size’ because Lemur does not do iterative layouts… one shot at laying things out.)

TLDR; setPreferredSize() is probably what you want instead of setSize() in this case.

Also, while passing “skin_inventory” to your GuiControl will cause no issue it’s also not doing anything for you and may be a misunderstanding of what that argument is for. Especially if you are only passing one string, might as well pass no strings at all:
new GuiControl().

For GUI elements with layers of components, this is what would control the order of those components… so doesn’t really make sense with only one argument. (And if you will not have any components anyway then no reason to have even one argument.)

Yep… This fixed it

My excuse: Coding at 3am…
Thanks

Also, how do I get the objects to render as if they were in a 3d space.
right now they are taking no account of the Z position.

It’s a byproduct of the gui bucket, unfortunately.

If you want to have a “gui node” that supports 3D objects then you have to create your own viewport + gui node (that is not in the Gui Bucket but part of an ortho camera)

I posted something recently on this so you might be able to search. I don’t remember if I added an example or not but maybe I did add one to the Lemur demos.

Yep:

I don’t remember if

You did!

@TooMuchJava you might find that usefull

With a bit of adjustements it might work (did it for me, when I tested it).

Thanks, the ViewportPanel looks like it will work, judging by the comments.

  • the filebrowser is/was my testcase for many things
    check that last picture in my post - you will see that 3dViewport worked for me as well.

Is there a prebuilt binary, or should I just add the library to my src folder?

You need to check and test. It did not work for me at the beginning and I needed to do some research in the forum (esp. for the 3D panel)
I will try to save you that. I am only using the following classes

SpatialAutoManager
SpatialUtil
ViewportPanel
ViewportPanel2D

You may need to change some references there.

I have not retested the following, but try: I also added a way to rotate the scene/model in the code below


  Container window = new Container();
  GuiNode.attachChild(window);
  window.setLocalTranslation(100,500,0);
      

 Spatial M1 = null;
  M1 = getApplication().getAssetManager().loadModel("Models/Oto/Oto.mesh.xml");
  AmbientLight L1 = new AmbientLight()

 Spatial M2 = null;
        M2 = getApplication().getAssetManager().loadModel("Models/Oto/Oto.mesh.xml");
        AmbientLight L2 = new AmbientLight();
...
  ViewportPanel Panel3D_01 = new ViewportPanel(getStateManager(),window.getElementId(),null);
   Panel3D_01.addLight(L1);
    Panel3D_01.attachScene(M1);
    //    M1.scale(8);
  Panel3D_01.setPreferredSize(new Vector3f(50,50,0));
window.addChild(Panel3D_01);
  


  ViewportPanel Panel3D_02 = new ViewportPanel(getStateManager(),window.getElementId(),null);
        Panel3D_02.addLight(L2);
        Panel3D_02.attachScene(M2);
     //   M2.scale(7);
        Panel3D_02.setPreferredSize(new Vector3f(100,100,0));
        window.addChild(Panel3D_02);
//        window.addChild(new Label ("Panel 2 Ende"));



        GuiGlobals.getInstance().getAnimationState().
                add(new TweenAnimation(true, Tweens.smoothStep(SpatialTweens.rotate(Panel3D_02.getViewportNode(), new Quaternion().fromAngleAxis(-FastMath.QUARTER_PI, Vector3f.UNIT_Y), new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y), 2)),
                        Tweens.smoothStep(SpatialTweens.rotate(Panel3D_02.getViewportNode(), new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y), new Quaternion().fromAngleAxis(-FastMath.QUARTER_PI, Vector3f.UNIT_Y), 2))));

2 Likes

Thanks for the code snippet. Extremely helpful.

I found in the ViewportPanel code:

//FIXME: When rotating, the bounds dimension can change, making the y be bigger than the x or z and viceverse, showing an undesired zoom-in-out effect.

Is there a workaround to this? Whenever I rotate my coin model, it enlarges, shrinks, enlarges, etc… Is there a fix?
EDIT: caching the bounding box after you add everything stops this behavior.

Could you be so kind and post the change here + as a bonus send a PR for the original Repository.

It could be a oneliner?

Post it where? Also, Yes, it was something like an 8-line fix
https://github.com/NemesisMate/UtilJME3-Lemur/pull/3

1 Like

In the PR the cacheBB() routine is never called. If I add a call here

                    if (cachedBB == null) {
                        bb = (BoundingBox) child.getWorldBound();
           -->              cacheBB();}
                    else bb = cachedBB;

it works (or at least the size of bounding box is constant).
Thanks for sharing the modifications

I was just calling it at a certain point to fix the box size. That does look like the smarter way to do it though.

Is there any way to set the clip area? it’s very annoying having half(or all) of your 3d model cut out of your ui.
Edit: This is a rotating skin(so you can see what you’re getting) but the spear on the end keeps going out of the clip area:

I have not done little more then the example above yet with the Viewportpanel as I allmost have no time to work on it - so I am not sure if I am a expert with that.
I once had an issue with the Viewport(VP) and its position not moving with the lemur element. In the end I found a solution by using
https://wiki.jmonkeyengine.org/jme3/advanced/multiple_camera_views.html
and made a PR that was accepted (so dont worry, this one was solved).

If you use the VPPanel then your whole scene is limited to the position and sice of the VP used. You may just adjust the position or size of the lemur element or you can try to work with the VP itself.
Dont forget you have a option to adjust your scene to the size of the VPPanel automatically but above, by caching the boundingbox, you just said use the first calculated boundingbox for the position of the scene (camera) inside your Viewport and Panel.
Somewhere you can turn that option off and set the position of the scene by yourself.

I am sorry at this point as I am not 100 % sure what your issue realy is and what could be the reasons.

I solved the problem: I was scaling content inside the viewport, along with the viewport itself, causing the model to pass out of the clip area