How Do I Get Multiple Views with AwtPanels?

On this forum, it seems like the most commonly-cited solution to those who want to run multiple views of jME3 in separate windows is to use the AwtPanel class. I see a lot of references to the TestAwtPanels.java code in the repository to provide guidance.

Fair enough. When I download and run this file, here’s what I see:

This is a step in the right direction, but I want two separate windows, each with its own view. So, based on extensive research (Googling, source code, browsing around the forum), I modify the TestAwtClass like so:

[java]import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TestAwtPanels extends SimpleApplication {

private static TestAwtPanels app;
private static AwtPanel panel, panel2;
private static int panelsClosed = 0;

private static void createWindowForPanel(AwtPanel panel, int location){
    JFrame frame = new JFrame("Render Display " + location);
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(panel, BorderLayout.CENTER);
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosed(WindowEvent e) {
            if (++panelsClosed == 2){
                app.stop();
            }
        }
    });
    frame.pack();
    frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400);
    frame.setVisible(true);
}

public static void main(String[] args){
    Logger.getLogger("com.jme3").setLevel(Level.WARNING);
   
    app = new TestAwtPanels();
    app.setShowSettings(false);
    AppSettings settings = new AppSettings(true);
    settings.setCustomRenderer(AwtPanelsContext.class);
    settings.setFrameRate(60);
    app.setSettings(settings);
    app.start();
   
    SwingUtilities.invokeLater(new Runnable(){
        public void run(){
            final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext();
            panel = ctx.createPanel(PaintMode.Accelerated);
            panel.setPreferredSize(new Dimension(400, 300));
            ctx.setInputSource(panel);
           
            panel2 = ctx.createPanel(PaintMode.Accelerated);
            panel2.setPreferredSize(new Dimension(400, 300));
           
            createWindowForPanel(panel, 300);
            createWindowForPanel(panel2, 700);
        }
    });
}

@Override
public void simpleInitApp() {
    flyCam.setDragToRotate(true);
   
    Node node1 = new Node("View 1");
    Node node2 = new Node("View 2");
    
    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    Geometry geom = new Geometry("Box", b);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    geom.setMaterial(mat);
    
    Box b2 = new Box(Vector3f.ZERO, 1, 1, 1);
    Geometry geom2 = new Geometry("Box", b2);
    Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat2.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    geom2.setMaterial(mat2);
    
    rootNode.attachChild(geom2);
    rootNode.attachChild(geom);
   
    Camera cam1 = cam.clone();
    ViewPort view1 = renderManager.createMainView("view1", cam1);
    view1.attachScene(node1);
    panel.attachTo(true, view1);
    
    Camera cam2 = cam.clone();
    ViewPort view2 = renderManager.createMainView("view1", cam2);
    view2.attachScene(node2);
    panel2.attachTo(false, view2);
    
    node1.updateLogicalState(speed);
    node1.updateGeometricState();
    node2.updateLogicalState(speed);
    node2.updateGeometricState();
    
    //panel.attachTo(true, viewPort);
   // guiViewPort.setClearFlags(true, true, true);
    //panel2.attachTo(false, guiViewPort);
}

}[/java]

and this is the result:

<p> </p>

Still not quite right. On this thread on using multiple 3D views, someone else claimed to run similar code that produces two windows, one with red boxes and one with blue boxes. I copied the code over and made the corrections the responders suggested (i.e. make one of the attachTo calls use a “false” parameter):

[java] import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MultipleViews extends SimpleApplication {
private static MultipleViews app;
private static AwtPanel panel1, panel2;
private static int panelsClosed = 0;
private static AwtPanelsContext ctx;

private static void createWindowForPanel(final AwtPanel panel, final int location)
{
JFrame frame = new JFrame("Render Display " + location);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {

    @Override
    public void windowClosed(WindowEvent e) {
        if (++panelsClosed == 2){
        app.stop();
        }
    }

    public void windowActivated(WindowEvent e) {
        System.out.println(location + " activated");
        ctx.setInputSource(panel);
    }
});

frame.pack();
//frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 900);
frame.setLocation(location, 100);
Window window = SwingUtilities.getWindowAncestor(frame);
if (window != null) window.revalidate();
frame.setVisible(true);

}

public static void main(String[] args){

app = new MultipleViews();
app.setShowSettings(false);
AppSettings settings = new AppSettings(true);
settings.setCustomRenderer(AwtPanelsContext.class);
settings.setFrameRate(60);
app.setSettings(settings);
app.start();
ctx = (AwtPanelsContext) app.getContext();

SwingUtilities.invokeLater(new Runnable(){
public void run()
{
panel1 = ctx.createPanel(PaintMode.Accelerated);
panel1.setPreferredSize(new Dimension(400, 300));

        panel2 = ctx.createPanel(PaintMode.Accelerated);
        panel2.setPreferredSize(new Dimension(400, 300));
        ctx.setInputSource(panel1);

        createWindowForPanel(panel1, 100);
        createWindowForPanel(panel2, 500);
    }
});

}

@Override
public void simpleInitApp()
{
Node rootNodeView1 = new Node(“rootNode View 1”);
Node rootNodeView2 = new Node(“rootNode View 2”);
rootNode.attachChild(rootNodeView1);
rootNode.attachChild(rootNodeView2);

flyCam.setDragToRotate(true);
flyCam.setMoveSpeed(100f);

Box b = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry geom1 = new Geometry("Box1", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom1.setMaterial(mat);
rootNodeView1.attachChild(geom1);

Box b2 = new Box(new Vector3f(5, 0, 0), 1, 1, 1);
Geometry geom2 = new Geometry("Box2", b2);
Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setColor("Color", ColorRGBA.Red);
geom2.setMaterial(mat2);
rootNodeView2.attachChild(geom2);

Camera cam1 = cam.clone();
ViewPort vp1 = renderManager.createMainView("view1", cam1);
//vp1.setClearFlags(true, true, true);
vp1.attachScene(rootNodeView1);
panel1.attachTo(false, vp1);

Camera cam2 = cam.clone();
ViewPort vp2 = renderManager.createMainView("view2", cam2);
//vp2.setClearFlags(true, true, true);
vp2.attachScene(rootNodeView2);
panel2.attachTo(true, vp2);


rootNodeView1.updateLogicalState(speed);
rootNodeView1.updateGeometricState();
rootNodeView2.updateLogicalState(speed);
rootNodeView2.updateGeometricState();

}
}
[/java]

and I still get everything in one window:

I’m completely stuck. How do I use AwtPanels to create two views in two separate windows? I would appreciate any help you can give. Thanks in advance!

1 Like

I activated your setClearFlags for the two viewports and i see a blue box in the one window and the red in the other.

Image and video hosting by TinyPic

I can assure that rendering into multiple windows works ;). I use it myself for my application.

1 Like

You know, I suspected it was the one thing I hadn’t tried yet. I had assumed that since the coder in the other post had left the setClearFlags() lines commented out, then they must have been commented out for a reason. Anyway, I tried your solution, and it worked.

Out of curiosity, what exactly does the setClearFlags() method do? I’m not entirely clear on this, even after reading the Javadoc.

With a little more work, I was able to get the program running with two panels with two different views, each with independent flyCam controls. That is, I can move around in one panel without affecting the state of the other. I’ve posted my code below. Can anyone tell me if they see any potential problems with it? In particular, is there any way to control the two flyCams independently that’s more robust/elegant than the process I’m using?

Thanks again for all of your help. This community is awesome!

[java]package multipleviews;

import com.jme3.app.SimpleApplication;
import com.jme3.input.FlyByCamera;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MultipleViews extends SimpleApplication {
private static MultipleViews app;
private static AwtPanel panel1, panel2;
private static int panelsClosed = 0;
private static AwtPanelsContext ctx;

private static void createWindowForPanel(final AwtPanel panel, final int location)
{
JFrame frame = new JFrame("Render Display " + location);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

frame.addWindowListener(new WindowAdapter() {

    @Override
    public void windowClosed(WindowEvent e) {
        if (++panelsClosed == 2){
        app.stop();
        }
    }

    @Override
    public void windowActivated(WindowEvent e) {
        ctx.setInputSource(panel);
        panel.requestFocusInWindow();            
    }
    
});

frame.pack();
frame.setLocation(location, 100);
frame.setVisible(true);

}

public static void main(String[] args){

app = new MultipleViews();
app.setShowSettings(false);
AppSettings settings = new AppSettings(true);
settings.setCustomRenderer(AwtPanelsContext.class);
settings.setFrameRate(60);
app.setSettings(settings);
app.start();
ctx = (AwtPanelsContext) app.getContext();

SwingUtilities.invokeLater(new Runnable(){
@Override
public void run()
{
panel1 = ctx.createPanel(PaintMode.Accelerated);
panel1.setPreferredSize(new Dimension(400, 300));

        panel2 = ctx.createPanel(PaintMode.Accelerated);
        panel2.setPreferredSize(new Dimension(400, 300));

        createWindowForPanel(panel1, 100);
        createWindowForPanel(panel2, 500);
    }
});

}

@Override
public void simpleInitApp()
{
Node rootNodeView1 = new Node(“rootNode View 1”);
Node rootNodeView2 = new Node(“rootNode View 2”);
rootNode.attachChild(rootNodeView1);
rootNode.attachChild(rootNodeView2);

flyCam.setEnabled(false);

Box b = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry geom1 = new Geometry("Box1", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geom1.setMaterial(mat);
rootNodeView1.attachChild(geom1);

Box b2 = new Box(new Vector3f(5, 0, 0), 1, 1, 1);
Geometry geom2 = new Geometry("Box2", b2);
Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setColor("Color", ColorRGBA.Red);
geom2.setMaterial(mat2);
rootNodeView2.attachChild(geom2);

Camera cam1 = new Camera(cam.getWidth(), cam.getHeight());
cam1.copyFrom(cam);
final FlyByCamera flyCam1 = new FlyByCamera(cam1);
flyCam1.setMoveSpeed(5);
flyCam1.registerWithInput(inputManager);
flyCam1.setDragToRotate(true);
flyCam1.setEnabled(false);   

Camera cam2 = new Camera(cam.getWidth(), cam.getHeight());
cam2.copyFrom(cam);  
final FlyByCamera flyCam2 = new FlyByCamera(cam2);
flyCam2.setMoveSpeed(5);
flyCam2.registerWithInput(inputManager);
flyCam2.setDragToRotate(true);
flyCam2.setEnabled(true);    

ViewPort vp1 = renderManager.createMainView("view1", cam1);
vp1.setClearFlags(true, true, true);
vp1.attachScene(rootNodeView1);
panel1.attachTo(false, vp1);

ViewPort vp2 = renderManager.createMainView("view2", cam2);
vp2.setClearFlags(true, true, true);
vp2.attachScene(rootNodeView2);
panel2.attachTo(true, vp2);

panel1.addFocusListener(new AwtPanelFocus(panel1, flyCam1));
panel2.addFocusListener(new AwtPanelFocus(panel2, flyCam2));

rootNodeView1.updateLogicalState(speed);
rootNodeView1.updateGeometricState();
rootNodeView2.updateLogicalState(speed);
rootNodeView2.updateGeometricState();

}
}

class AwtPanelFocus implements FocusListener
{
AwtPanel panel;
FlyByCamera flyCam;

public AwtPanelFocus(AwtPanel panel, FlyByCamera flyCam)
{
    this.panel = panel;
    this.flyCam = flyCam;
}

@Override
public void focusGained(FocusEvent e)
{
    flyCam.setEnabled(true);
}

@Override
public void focusLost(FocusEvent e) 
{
    flyCam.setEnabled(false);
}

}[/java]

@JCSW said: Out of curiosity, what exactly does the setClearFlags() method do? I'm not entirely clear on this, even after reading the Javadoc.

It sets the clear flags for the viewport, ie: the flags that control how the viewport is cleared before rendering. Should it clear the color buffer? Should it clear the depth buffer? Should it clear the stencil buffer? There are boolean flags to control that.

…not sure how much clearer I can be as it seems I’m just restating the method name or what the javadoc says. So I’m not sure I’m being helpful.

Perhaps you can tell us what is unclear about:

Set the clear flags (color, depth, stencil) in one call.
Parameters:
    color - If color buffer clearing should be enabled.
    depth - If depth buffer clearing should be enabled.
    stencil - If stencil buffer clearing should be enabled.</blockquote>

Well, if you don’t know what those things are and what the clearing actually does then I can see it still being confusing. It assumes domain knowledge that may not be there.

@zarch said: Well, if you don't know what those things are and what the clearing actually does then I can see it still being confusing. It assumes domain knowledge that may not be there.

Exactly. I understand the basic, abstract concept of a buffer as it applies to graphics rendering, but nothing beyond that. What are color, depth, and stencil buffers, and what does it mean to clear them? I assumed that this would be the default mode (i.e. why would you not want to clear the buffers?). And why is it that when I don’t call setClearFlags(), I get the rendering problems described in my earlier post?

Thanks again!

You can use a viewport to render many layers of a scene. So sometimes you don’t want to clear anything. Sometimes you only want to clear depth and leave color, etc…

If you don’t know what the color buffer and depth buffer are then you may want to take a tour through some basic OpenGL documentation at some point. The fundamentals are good to know for lots of reasons.