Local Info

Topics

j3d.org

Layers and Viewports

One of the major changes in the move to Aviatrix3D 2.0 was the introduction of a full layering and compositing capability to the API. This changed the root of the scene graph structure to always use this capability, regardless of whether you need it or not. In the previous tutorials you have seen a basic setup using just a single layer and viewport. Aviatrix3D is capable of far more than that.

If you would like to see the complete version of these examples, they can be found in the examples/layers directory.

 

Organising the Root of the Scene Graph

At the root of the scene graph you have layers. Each layer represents an independent set of geometry that can be rendered. Layers present a logical rendering order of how content should be placed on the screen or surface. Each layer has its own viewpoint, background, global fog etc, which act independently of all the other layers. A layer logically covers the entire drawable component.

The layer is a bucket for this global rendering order. Within each layer you need to describe how much of the drawable component you want to make your active drawing area. This active drawing area is called a viewport. Depending on which layer type you select, you could have more than one viewport in a single layer. Each viewport represents a single, fixed-size amount of screen that your geometry will be rendered in. Size is always measured in pixels and described in the surface coordinate system (bottom, left, width and height). With multiple viewports in a layer, you can then have a collection of different sets of geometry all being rendered at once, without needing to get into using multiple window components.

To add a little further complexity, we also allow each viewport to potentially have layers. In this case the layers are all restricted to the viewport and always take that viewport's size. Why would we allow this? Well a lot of cases where you want multiple viewports, you also want to have per-viewport user interface capabilities. Think of your typical 4-view graphical editor application such as AutoCAD or Maya. Each view of the model (viewport in our terminology) has a small UI toolbar that indicates various local per-view options. These are rendered as layers within that viewport. Thus, we set up our scene graph structures to allow this sort of application environment as well.

One minor departure that layers take from the rest of the scene graph is that we do not allow you to share them. All layers and viewports have a single parent object.

Defining Multiple Layers

In the hello world example, you saw code that set up a single viewport and layer to render the geometry in. The code looked like this:

SimpleViewport view = new SimpleViewport();
view.setDimensions(0, 0, 500, 500);
view.setScene(scene);

SimpleLayer layer = new SimpleLayer();
layer.setViewport(view);

Layer[] layers = { layer };
sceneManager.setLayers(layers, 1);

The last line is the interesting part. Notice that the second parameter says how many layers are being passed in from the array. Having multiple layers is as simple as replicating the above code for as many layers as you want. Layers are defined in rendering order, so index 0 is the first layer to be rendered, up to layer n, which is the last rendered.

A simple example of this is the MultiLayerDemo. In that we have three layers defined. In the middle layer we animate the object so that you can see how the layering effect works. The red triangle is in the rear layer and blue triangle in the front. The middle layer has the animated green triangle showing how the compositing works. The code looks remarkably familiar:

SimpleViewport view1 = new SimpleViewport();
view1.setDimensions(0, 0, 800, 800);
view1.setScene(scene1);

SimpleViewport view2 = new SimpleViewport();
view2.setDimensions(0, 0, 800, 800);
view2.setScene(scene2);

SimpleViewport view3 = new SimpleViewport();
view3.setDimensions(0, 0, 800, 800);
view3.setScene(scene3);

SimpleLayer layer1 = new SimpleLayer();
layer1.setViewport(view1);

SimpleLayer layer2 = new SimpleLayer();
layer2.setViewport(view2);

SimpleLayer layer3 = new SimpleLayer();
layer3.setViewport(view3);

Layer[] layers = { layer1, layer2, layer3 };
sceneManager.setLayers(layers, 3);

ObjectLayerAnimation anim = new ObjectLayerAnimation(layer2_group);
sceneManager.setApplicationObserver(anim);

Each layer is created with a viewport and the geometry is placed as part of it. These are all then collected into an array and passed to the rendering manager.

Multiple Viewports in a Layer

For applications that want to have more than a single viewport within a layer, you make use of a different class. The SimpleLayer class used in the previous examples is just that - all it handles is a single viewport. If you would like to make use of multiple viewports you need to use the CompositeLayer class. The example used in this section is the MultiViewportDemo class.

A composite layer holds onto a collection of viewports. As the layer itself does not have any particular intelligence, all it needs to do is hold viewports. In the composite case, that means just a collection of them, so you have the corresponding add and remove methods. Becuase it is the viewport itself that describes screen location, the order that you add them to the containing layer is inconsequential. The following code creates 4 viewports and adds then to a single layer that is rendered by the application.

// Then the viewports. Divide the screen up into 4 viewports
SimpleViewport view1 = new SimpleViewport();
view1.setDimensions(0, 0, 400, 400);
view1.setScene(scene1);

SimpleViewport view2 = new SimpleViewport();
view2.setDimensions(400, 0, 400, 400);
view2.setScene(scene2);

SimpleViewport view3 = new SimpleViewport();
view3.setDimensions(0, 400, 400, 400);
view3.setScene(scene3);

SimpleViewport view4 = new SimpleViewport();
view4.setDimensions(400, 400, 400, 400);
view4.setScene(scene4);

CompositeLayer layer = new CompositeLayer();
layer.addViewport(view1);
layer.addViewport(view2);
layer.addViewport(view3);
layer.addViewport(view4);

Layer[] layers = { layer };
sceneManager.setLayers(layers, 1);

Note how the pixel sizes all appear to have nice round numbers. Pixels are zero based, so a width of 400 goes from pixel 0 to 399. Pixel 400 is the start of the next area. We don't do any sanity checking of the viewport bounds, so it is perfectly legal to have one viewport overlap another. The rendering order is not guaranteed, so it's best not to do this. If you need those sort of effects, you're better off having several layers, each with their own viewport.

Just because you have multiple viewports, does not mean you are required to have separate scene graphs for each viewport. The Scene object itself cannot be shared, but you can share the contents of the scene graph by simply placing a SharedNode or SharedGroup at the root of the common piece of scene graph to show and adding that to the appropriate scene or other node. The example app does exactly this, with separate viewpoints defined for each scene, so that you can see that each is independent. When you run the example, this is what it looks like:

Layers in a Viewport

If you are running multiple viewports, it may be useful to have individual layers within a viewport. As it has been mentioned before, this allows you to get creative within a single viewport for providing effects such as local user interfaces etc.

In the previous examples we have used a viewport that doesn't have any layers - it directly takes the scene to be rendered. To allow for multiple layers in a viewport, the CompositeViewport is used. To create layers with the viewport, you then need to use one of the classes derived from the ViewportLayer base class. Notice that it does not take the same Layer class as the root, so as to avoid allowing the definition of viewports in layers in viewports in layers etc.

Setting up a viewport with layers is mostly just a reorganisation of the techniques you've already seen. The main difference is that there is no need to specify an addition viewport inside each layer that is rendered in the viewport. All layers take up the full size of the viewport that they are contained in. The following code snippet is taken from the ViewportLayers example.

SimpleViewportLayer layer1 = new SimpleViewportLayer();
layer1.setScene(scene1);

SimpleViewportLayer layer2 = new SimpleViewportLayer();
layer2.setScene(scene2);

SimpleViewportLayer layer3 = new SimpleViewportLayer();
layer3.setScene(scene3);

CompositeViewport view = new CompositeViewport();
view.setDimensions(0, 0, 800, 800);
view.addViewportLayer(layer1);
view.addViewportLayer(layer2);
view.addViewportLayer(layer3);

SimpleLayer main_layer = new SimpleLayer();
main_layer.setViewport(view);

Layer[] layers = { main_layer };
sceneManager.setLayers(layers, 1);

Like the main layers, viewport layers use the order that they are added to define the rendering order. The first one added is the rear-most layer, and the last added is at the front.

Changing Viewport sizes

If you have played with any of the examples so far, you may have noticed that any time you change the window size, the drawn area did not change. Resizing the window to be bigger left a black section on the right and bottom (we're using grey backgrounds for all of them). The reason for this is that we don't automatically resize the viewport as the containing component changes size. There are many reasons for this design decision, but the principle one is that we wanted to leave as much flexibility to the end user as possible about how they may want to resize the views - especially in applications that want to have multiple viewports in a single layer.

However, we do realise that resizing the viewport in response to the canvas size changes is an extremely important capability, so we also designed an abstract system and some convenience classes that take care of most of the behaviour for you.

One of the design goals of these convenience classes (and Aviatrix3D in general) was to separate your code from the underlying window toolkits as much as possible. Moving from AWT to Swing to SWT should mean absolute minimal changes for you. To enable this, there is a listener that can be registerd with your GraphicsOutputDevice named GraphicsResizeListener. You can register an instance of this listener with the output device interface and it will tell you when the surface changes size, allowing you to update your viewport size as needed.

For further convenience, we also provide a class that will do the viewport size management for you as well. The class ViewportResizeManager can be handed a list of viewports to manage, and can be registered as the resize listener with the surface. The only work your code needs to do is tell it to check each frame for updates. Any updates that came in asynchronously are then handled through the normally callback listener updates as required by Avaitrix3D.

SimpleLayer layer1 = new SimpleLayer();
layer1.setViewport(view1);

SimpleLayer layer2 = new SimpleLayer();
layer2.setViewport(view2);

SimpleLayer layer3 = new SimpleLayer();
layer3.setViewport(view3);

Layer[] layers = { layer1, layer2, layer3 };
sceneManager.setLayers(layers, 3);

ViewportResizeManager resize_manager = new ViewportResizeManager();
resize_manager.addManagedViewport(view1);
resize_manager.addManagedViewport(view2);
resize_manager.addManagedViewport(view3);

surface.addGraphicsResizeListener(resize_manager);

ResizeLayerUpdater anim = new ResizeLayerUpdater(resize_manager);

sceneManager.setApplicationObserver(anim);

Inside the layer updater, you have the following code - which should look like a fairly familiar pattern:

public class ResizeLayerUpdater
    implements ApplicationUpdateObserver, NodeUpdateListener
{
    private ViewportResizeManager resizeManager;

    public ResizeLayerUpdater(ViewportResizeManager resizer)
    {
        resizeManager = resizer;
    }

    public void updateSceneGraph()
    {
        resizeManager.sendResizeUpdates();
    }

    public void appShutdown()
    {
        // do nothing
    }
}

You can see this class in action in the ViewportResizeDemo example.

That's all there is to layers. If you want to see all of them running together, take a look at the example CombinedLayersDemo. You should see the following output: