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): Lemur/ViewPortDemoState.java at master · jMonkeyEngine-Contributions/Lemur · GitHub

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;
    }

}