Trouble with viewport with its own rootNode

At

is to be read

… or you can give each camera a special node whose content it can see:

viewPort2.attachScene(spookyGhostDetectorNode);

Unfortunately, it doesn’t explain why this leads to an error first.

Better:

  Node spookyGhostDetectorNode = new Node("spookyGhostDetectorNode");
  spookyGhostDetectorNode.updateGeometricState();
  viewPort2.attachScene(spookyGhostDetectorNode);

I suggest completing the documentation.

1 Like

The tutorial probably assumes that the other node is also in the main scene (a common use-case for viewports is to view a subgraph of the scene)… in which case your line is not required.

In all other cases, it’s more complicated than your one line hack because it requires constant management every frame to call both updateLogicalState and updateGeometricState. (Search the forum for ViewportAppState or something like that.)

…so the documentation could be better here but better is also more complicated.

2 Likes

You mean maybe RootNodeAppState?

Incidentally, that would be a text for the hotfix of the documentation:

That would warn one or the other.

By the way (when I see SimpleApplication.update() ) it doesn’t have to look RootNodeAppState like this:

 @Override
    public void update(final float tpf
    ) {
        super.update(tpf);

        AppProfiler prof = getApplication().getAppProfiler();
        try {
            if (prof != null) {
                prof.appStep(AppStep.SpatialUpdate);
            }
            rootNode.updateLogicalState(tpf);
            rootNode.updateGeometricState(); 
        } finally {
            if (prof != null) {
                prof.appStep(AppStep.StateManagerUpdate);
            }
        }
    }

No. I mean an app state that can manage a viewport.

The problem with searching on the forum now is that there are officially way more times of me suggesting that someone searches for it that overwhelm the original result.

Lemur has a demo class that manages a viewport (in addition to some other demo-specific stuff): https://github.com/jMonkeyEngine-Contributions/Lemur/blob/master/examples/demos/src/main/java/demo/ViewPortDemoState.java

1 Like

When you’ve got a solution worked out, I’m sure we all look forward to seeing your patch submission for the docs.

Thank very much, the link to the Lemut example helped a lot.
I no longer have to manually update the geometry.

My SplitterVertical extends BaseAppState works fine except for one other problem (But I should certainly do some research and start a new post if necessary):

The camera’s lens is deformed with splitter.

I pause main camera, but don’t change it. I only manipulate my two copies of the cameras.

Code:

package jare.view.splitting;

import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.input.InputManager;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import static jare.basic.JaReConst.MOUSE_BUTTON_LEFT;
import static jare.basic.JaReConst.MOUSE_MOVE;
import jare.basic.JaReKeybinding;

/**
 *
 * @author Janusch Rentenatus
 */
public class SplitterVertical extends BaseAppState {

    private final Node rootNode1;
    private final Camera camera1;
    private ViewPort view1;
    private final Node rootNode2;
    private final Camera camera2;
    private ViewPort view2;
    Geometry splitter;
    private float splittPos;
    private boolean moving;

    public SplitterVertical(Application app, Node guiNode, Camera masterCam1, Camera masterCam2) {
        super("SplitterV");

        splittPos = 0.8f;
        moving = false;
        rootNode1 = new Node("SplitterRootNode1");
        camera1 = masterCam1.clone();
        camera1.setViewPort(0.0f, splittPos, 0.0f, 1.0f);
        view1 = app.getRenderManager().createMainView("Splitter1", camera1);
        view1.setClearFlags(true, true, true);
        view1.attachScene(rootNode1);
        view1.setBackgroundColor(new ColorRGBA(0.2f, 0.15f, 0.2f, 1.0f));

        rootNode2 = new Node("SplitterRootNode2");
        camera2 = masterCam2.clone();
        camera2.setViewPort(splittPos, 1.0f, 0.0f, 1.0f);
        view2 = app.getRenderManager().createMainView("Splitter2", camera2);
        view2.setClearFlags(true, true, true);
        view2.attachScene(rootNode2);
        view2.setBackgroundColor(new ColorRGBA(0.15f, 0.2f, 0.2f, 1.0f));

        int w = app.getCamera().getWidth();
        int h = app.getCamera().getHeight();
        Mesh mesh = createSplitterMesh(w, h);
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        splitter = new Geometry("SplitterBar", mesh);
        splitter.setMaterial(mat);
        guiNode.attachChild(splitter);
        splitter.setLocalTranslation(w * splittPos, 0, 98f);
    }

    private Mesh createSplitterMesh(float h, float w) {
        float[] vertexbuffer = new float[]{ //
            -2f, 0, 0, //
            2f, 0, 0,//
            -2f, h, 0,//
            2f, 0, 0,//
            2f, h, 0,//
            -2f, h, 0,//
        };
        final Mesh triMesh = new Mesh();
        triMesh.setMode(Mesh.Mode.Triangles);
        triMesh.setBuffer(VertexBuffer.Type.Position, 3, vertexbuffer);
        triMesh.updateBound();
        return triMesh;
    }

    private final AnalogListener mouseMoveListener = (name, keyPressed, tpf) -> {
        if (moving) {
            setPos(calcPos());
        }
    };

    private final ActionListener startMoveListener = (name, keyPressed, tpf) -> {
        if (MOUSE_BUTTON_LEFT.equals(name)) {
            if (keyPressed) {
                moving = isStartMove();
            } else if (moving) {
                resetViewports();
                moving = false;
            }
        }
    };

    private boolean isStartMove() {
        Application app = getApplication();
        final Vector2f click2d = app.getInputManager().getCursorPosition();
        float width = app.getCamera().getWidth();
        float pos = width * splittPos;
        return (click2d.x - 2 < pos && click2d.x + 2 > pos);
    }

    private float calcPos() {
        Application app = getApplication();
        final Vector2f click2d = app.getInputManager().getCursorPosition();
        float width = app.getCamera().getWidth();
        return click2d.x / width;
    }

    private void setPos(float newPos) {
        Application app = getApplication();
        float width = app.getCamera().getWidth();
        splittPos = newPos;
        splitter.setLocalTranslation(width * splittPos, 0, 98f);
    }

    private void resetViewports() {
        //Application app = getApplication();
        //int width = app.getCamera().getWidth();
        //int height = app.getCamera().getHeight();
        //int split = (int) (width * splittPos);
        camera1.setViewPort(0.0f, splittPos, 0.0f, 1.0f);
        camera2.setViewPort(splittPos, 1.0f, 0.0f, 1.0f);
    }

    @Override
    protected void initialize(Application arg0) {
        initMyKeys();
    }

    /**
     * Custom Keybinding: Map named actions to inputs.
     */
    private void initMyKeys() {
        final InputManager inputManager = getApplication().getInputManager();
        JaReKeybinding.initInputManager(inputManager);

        // Add the names to the asction listener.
        inputManager.addListener(mouseMoveListener, MOUSE_MOVE);
        inputManager.addListener(startMoveListener, MOUSE_BUTTON_LEFT);

    }

    @Override
    protected void cleanup(Application arg0) {
        // noop
    }

    @Override
    protected void onEnable() {
        // noop
    }

    @Override
    protected void onDisable() {
        splitter.removeFromParent();
        disposeViewPort();
    }

    @Override
    public void update(final float tpf) {
        super.update(tpf);

        rootNode1.updateLogicalState(tpf);
        rootNode2.updateLogicalState(tpf);

    }

    @Override
    public void render(RenderManager rm) {
        super.render(rm);

        rootNode1.updateGeometricState();
        rootNode2.updateGeometricState();
    }

    protected void disposeViewPort() {
        if (view1 == null || view2 == null) {
            return;
        }
        getApplication().getRenderManager().removePostView(view1);
        getApplication().getRenderManager().removePostView(view2);
        view1 = view2 = null;
    }

    public Node getRootNode1() {
        return rootNode1;
    }

    public Camera getCamera1() {
        return camera1;
    }

    public ViewPort getView1() {
        return view1;
    }

    public Node getRootNode2() {
        return rootNode2;
    }

    public Camera getCamera2() {
        return camera2;
    }

    public ViewPort getView2() {
        return view2;
    }

}

Edit Oct 15 : Problem with camera update is solved.

Unfortunately, my own node is not yet equivalent.

Any lemur updates are missing.

In the gui node we see a container with a spring layout and three different labels. I would have expected the same in my node.

image

my own node only shows my container, because I am with

System.out.println(con.getPreferredSize().getX());

force a calculation, otherwise nothing will be displayed. Layout is still not calculated.

Unfortunately I use Lemur in the rootNode and in the guiNode.
Which updates do I still have to call up in my state?

The same ones in the app state I already linked.

1 Like

Oooh, sorry. I installed this error myself: For the second view, the method
updateLogicalState (tpf);
was not called. I could have saved myself this time for troubleshooting. Shit happens.

For completeness:
If in the new Viewport Lemur Buttons should also work, then the mouse or touchpad must also be connected to the new viewport:

public interface LemurableView {

    public default void addCollisionRoot(Application app, ViewPort viewWithLemurComponents) {
        MouseAppState mas = app.getStateManager().getState(MouseAppState.class);
        if (mas != null) {
            mas.addCollisionRoot(viewWithLemurComponents, viewWithLemurComponents.getName() + "View");
        }
        TouchAppState tas = app.getStateManager().getState(TouchAppState.class);
        if (tas != null) {
            tas.addCollisionRoot(viewWithLemurComponents, viewWithLemurComponents.getName() + "View");
        }
    }

    public default void removeCollisionRoot(Application app, ViewPort viewWithLemurComponents) {
        MouseAppState mas = app.getStateManager().getState(MouseAppState.class);
        if (mas != null) {
            mas.removeCollisionRoot(viewWithLemurComponents);
        }
        TouchAppState tas = app.getStateManager().getState(TouchAppState.class);
        if (tas != null) {
            tas.removeCollisionRoot(viewWithLemurComponents);
        }
    }

}

From any JME BaseAppState subclass (which is usually the case for anything setting viewports up…)

BasePickState pickState = getState(BasePickState.class).addCollisionRoot(viewWithLemurComponents);

There will only ever be a mouse state or a touch state but never both so might as well just use the common base class.

Also, the layer stuff is probably unnecessary, too. It’s for cases where a scene might have a 2D gui and a 3D pickable part in the same scene/viewport. And then you would need to either use the standard layers or configure your own layer order. Probably you don’t need it.

2 Likes

Thank you very much. Simplified my code to BasePickState.

I am recreating Swing with Lemur. Each panel with its own paints must be its own camera and view and root node, otherwise the painting is not independent.

Project: Tinker an xml editor with JME3?

Some views are real 3D (but sometimes with Lemur in them too), some are actually just gui. Since the botth (maybe 3d, maybe 2d) in the SplitPane has to be independent, I do each Panel as its own camera view. GuiViewPort is only withheld to the popup menus and the splitter bars.

I’m thinking about TabbedPane, multiple views, but only one active …