2. Using Jazz

Now that you've seen the basic components of Jazz, and have created a simple program, lets look at making a more interesting scene. We'll start with one of the most important node types - ZTransformGroup which allows you to move things within the Jazz world. 

2.1. Transforms

Jazz specifies all transformations of objects and cameras with standard 2D affine transformations. Affine transforms map 2D coordinate to other 2D coordinates in a manner that preserves straightness and "parallelness" of lines. Affine transforms can represent any combination of translation, scale, rotation, and shear. In Jazz, affine transforms are implemented using standard Java2D affine transforms. They are contained within the ZTransformGroup group node. Inserting one of these nodes in the scenegraph applies its transform to the entire subtree below that node.

Affine transforms consist of 6 numbers which are typically represented in a 3x3 matrix where multiplying the transform T by the vector V results in V'.

         [V'] = [T][V]
     or
         [x']   [m00 m01 m02] [x]   [m00x + m01y + m02] 
         [y'] = [m10 m11 m12] [y] = [m10x + m11y + m12] 
         [1 ]   [  0   0   1] [1]   [1]
The documentation for ZTransformGroup explains affine transforms in more detail. ZTransformGroup has several methods for modifying aspects of the transform, such as scale, rotate, and translate. It is also possible to animate those changes over time with those methods - or to specify the new transform with a standard Java2D AffineTransform. 

2.1.1. Transforming Visual Elements

The Jazz scenegraph specifies a hierarchy of objects that exist in the Jazz global coordinate system. When objects are created as visual components within nodes, they exist at their default position in the world. For instance, an image is positioned with its top-left corner at the origin extending to the right and down. If you want the image to appear somewhere else, it must be moved (also called 'transformed').

In Jazz, ZVisualComponent which is used to represent visual elements, do not have the ability to be transformed. Instead, a transform node must be inserted above the node that contains the visual component. The transform applies to the entire subtree of that node. So, if there are several children of that node, a single transform node will apply to all of those children. Note that a single visual component can be reused in several places in the scenegraph. If those places are each transformed differently, a single visual component can appear in multiple places.

There are two equally valid ways of thinking about the effects of transforms. The first (and typical) way of thinking about transforms is that the transform changes the place within the global coordinate system that the affected objects appear at. For instance, if you have a transform node 'transformNode', and you call:

     transformNode.translate(50.0f, 0.0f);
The result is that the children of 'transformNode' (and their descendants) all get translated 50 units to the right.

Alternatively, you can think of transforms as representing nested coordinate systems. Then, that same call to translate would result in 'transformNode' being associated with a new coordinate system that is shifted 50 units to the right of the global coordinate system. Then, the visual component associated with transformNode and all of its children would appear at their normal places within the new coordinate system, but to find out where those objects are in global coordinates, you would have to transform the local coordinate system to global coordinates.

Jazz has several utility methods for converting between different coordinate systems. By definition, we say that the root of the Jazz scenegraph is in global coordinates. Every other node is in its own local coordinate system. These utility methods (such as ZNode.localToGlobal and ZNode.globalToLocal) convert points and rectangles from one coordinate system to the other. 

2.1.2. Transforming Cameras

Just as transforms are used to modify visual elements within a scenegraph, transforms can be used to modify where a camera looks onto the scenegraph. ZCamera has a view transform with methods for manipulating the view which are very similar to those in ZTransformGroup. These methods let you translate, scale, rotate, and animate camera view changes.

The key thing to keep in mind here is that transforming internal nodes have the effect of moving objects within the scenegraph while transforming the camera doesn't affect the scenegraph, but instead changes where a particular camera looks onto the world represented by the scenegraph. 

2.2. Edit Groups

For an application to use a transform as just described, it would have to create an instance of a transform node, and insert it into the scenegraph in the appropriate place. Similarly, an application might insert other node types, such as fade or selection nodes above a particular node in order to add those features. This can become somewhat complicated because for each node that the application cares about, there could be a few extra nodes modifying that node. In addition, there is typically a preferred ordering to these nodes (i.e., transform goes above selection so that the selection moves with the transform).

To support applications in managing these sets of nodes, Jazz has a utility called an "editor" that manages these "edit groups". Lets start by thinking about a single node that an application knows about and wants to manipulate. The application first decides that it wants to add a transform to that node. It does this by getting the editor for that node, and requesting a transform:

    ZTransformGroup transformNode = node.editor().getTransformGroup();
This will create a new transform node, and insert it above the node. Editors are careful in managing these extra nodes, and the next time you ask for the transform of that node, it will return the same transform. We use the terminology that the node that we start with is called the "primary node", all the extra nodes that are created by the editor are called "edit nodes", and the collection of primary and edit nodes altogether are called an "edit group". Given any member of an edit group, you can access the primary node with editor.getNode(), and you can access the top-most member of the edit group with editor.getTop().

The editor manages these edit nodes types:

Edit groups are defined through the use of ZGroup's hasOneChild bit. Any group can have this bit set which then does not allow that group to have more than one child. Given a primary node, those immediate ancestors which have that bit set are considered to edit the primary node. The first ancestor which does not have that bit set starts a new edit group.

Jazz provides a default editor with ZSceneGraphEditor. An instance of the editor is created whenever node.editor() is called. However, an application can extend or change the definition of th editor by specifying that a different editor should be used. This is done by defining an application-specific editor, and calling the static method ZNode.setEditorFactory(), and specifying a factory that creates that special editor. 

2.3. Creating New Visual Components

As we saw in Section 1.2.1, every visual element is represented as a ZVisualComponent in the scenegraph. Jazz has several standard visual components, but applications may want to create their own. In order to create new component types, applications must extend ZVisualComponent. Minimally, an application must define: In addition, visual components will frequently decide to override: This tells Jazz how to "pick" the visual element. Picking is used to determine if the object is under the pointer. The default implementation for pick is just to check if the pointer is within the bounds of the component. If a new visual component's shape is not a filled rectangle that is the same as the bounds, then a new pick method should probably be defined. A simple example of this is a circle which should not be pickable outside of the circle.

Finally, the visual element is responsible for creating itself and for supporting manipulation of itself. We must look at the details of manipulating an object in some detail. This is because Jazz determines when to render an component based on its bounds, and because components are responsible for indicating to Jazz when they have changed through the repaint() and reshape() methods.

Every visual component must maintain a "model", or an internal data structure that represents the object. For a circle, this may be as simple as a center point, radius, and pen color. For other objects, the model may be more complex. Typical components will support methods that modify itself. The circle may have methods, for instance, to modify its pen width or pen color. It is the responsibility of these methods to indicate they have changed by calling repaint() if the object needs repainting, but the bounds have not changed (i.e., see ZCircle.setPenColor below) - or by calling reshape() if their bounds have changed (i.e., see ZCircle.setPenWidth below). 

2.3.1. An Example: ZCircle

ZCircle maintains a model of a Ellipse2D.Float whose height is equal to it's width. In addition, it also has a penWidth, penColor, and a fillColor

2.3.2. Sample Code

import java.awt.*;
import java.awt.geom.*;
import edu.umd.cs.jazz.*;
import edu.umd.cs.jazz.util.*;

public class ZCircle extends ZVisualComponent {
                                // default values for variables
    static final public Color  penColor_DEFAULT = Color.blue;
    static final public Color  fillColor_DEFAULT = Color.yellow;
    static final public float  penWidth_DEFAULT = 5.0f;
    
                                // member variables
    protected Color penColor = penColor_DEFAULT;
    protected Color fillColor = fillColor_DEFAULT;
    protected float penWidth = penWidth_DEFAULT;
    protected Ellipse2D.Float circle;

    /***************************** Constructors **********************************/
    
                                // creates circle at (0,0) with radius = 0
    public ZCircle () {
        circle = new Ellipse2D.Float();
        reshape();
    }
    
                                // creates circle with center (x,y) and radius = r
    public ZCircle(float x, float y, float r) {
        float half = r / 2;
        circle = new Ellipse2D.Float(x - half, y - half, r, r);
        reshape();
    }

                                // creates circle within bounding box defined by
                                // point (x,y) with width and height
    public ZCircle(float x, float y, float width, float height) {
        float smallerDim = (width < height ? width : height);
        circle = new Ellipse2D.Float(x, y, smallerDim, smallerDim);
        reshape();
    }
        

    /*********** Get/Set functions for variables specific to the circle *********/
    public float getPenWidth() { return penWidth; }
    public void setPenWidth(float p) {
        penWidth = p;
        reshape();              // Changing the pen width results in the bounds changing
    }

    public Color getPenColor() { return penColor; }
    public void setPenColor(Color c) {
        penColor = c;
        repaint();              // Changing the pen color does not result in a bounds change
    }

    public Color getFillColor() { return fillColor; }
    public void setFillColor(Color c) {
        fillColor = c;
        repaint();              // Changing the fill color does not result in a bounds change
    }

    public Point2D getCenter() {
        if (circle.getWidth() != circle.getHeight()) {
            System.out.println("ERROR: This is not a circle");
        }
        double x = circle.getX() + (circle.getWidth() / 2);
        double y = circle.getY() + (circle.getHeight() / 2);
        return (new Point2D.Float((float)x, (float)y));
    }


    /******************* paint, computeBounds, pick **************************/

                                // tell Jazz how to paint yourself
    public void paint(Graphics2D g2) {
        g2.setStroke(new BasicStroke(penWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
        if (fillColor != null) {
            g2.setColor(fillColor);
            g2.fill(circle);
        }
        if (penColor != null) {
            g2.setColor(penColor);
            g2.draw(circle);
        }
    }

                                // tell Jazz how big you are
    protected void computeBounds() {
                                // expand bounds to accomodate penWidth
        float p = penWidth;
        float p2 = 0.5f * penWidth;
        Rectangle2D rect = circle.getBounds2D();
        bounds.setRect((float)(rect.getX() - p2), (float)(rect.getY() - p2),
                       (float)(rect.getWidth() + p), (float)(rect.getHeight() + p));
    }

                                // tell Jazz how to pick you
                                // this function does not need to be overloaded
                                // but it is a good idea to do so
    public boolean pick(Rectangle2D rect, ZSceneGraphPath path) {
        boolean picked = false;
        if (fillColor != null) {   // If there is a fill color, then pick the inside of the circle
            picked = circle.contains(rect);
        } else if (penColor != null) {
                                   // Else, if there is a pen color, pick just the perimeter of the circle
            float px = (float)(rect.getX() + (0.5f * rect.getWidth()));
            float py = (float)(rect.getY() + (0.5f * rect.getHeight()));
            Point2D pt = new Point2D.Float(px, py);
            double distance = pt.distance(getCenter());
            double radius = circle.getWidth() / 2;
            if ((distance >= (radius - penWidth/2)) && (distance <= (radius + penWidth/2))) {
                picked = true;
            }
        }

        return picked;
    }
}

2.4. Adding Custom Event Handlers

User interaction is a fundamental part of any program and in Jazz this is no exception. Jazz directly supports event processing by allowing applications to create event handlers for mouse events, and attach them directly to any node in the Jazz scenegraph. If a user clicks on a visual component, any nodes it is attached to will be searched for event handlers, and if there any, they will be fired. Then, if the event is not consumed, the event will percolate up the tree firing event handlers as it goes up until either the event is consumed, or it reaches the root.

With this percolation model, applications can write an event handler for a specific node by putting the event handler on that node. Or, it can write an event handler for all nodes by putting the event handler at the root of the tree. Finally, an application can write a different event handler for each camera by putting the event handler on the node that contains the camera.

Jazz has several standard event handlers which can be used directly by applications. They are:

The built-in event handlers are straight-forward to use. They must simply be instantiated (attaching it to the appropriate node in the scenegraph), and activated. For instance, ZCanvas attaches the pan and zoom event handlers by default by doing this:
    panEventHandler = new ZPanEventHandler(cameraNode);
    zoomEventHandler = new ZoomEventHandler(cameraNode);
    panEventHandler.setActive(true);
    zoomEventHandler.setActive(true);
Obviously, it will become necessary to provide your own event handlers for the many things that you will want your application to do. Thus Jazz provides ZEventHandler which is an interface that may be implemented when defining Jazz event handlers. Although the use of ZEventHandler is not required, it is useful because that way a single object can be marked as being an event handler, and it can be passed around, and made active or inactive.

When writing per-node Jazz event handlers, it is important to understand the algorithm that Jazz follows to fire event handlers when events arrive. For each mouse or mouse motion event, Jazz calls ZDrawingSurface.pick which identifies the object that the pointer was over. The path returned by the pick call is used to identify event handlers as follows:

Starting at the end of the path, the first node is found and

  1. Each event handler on that node is fired starting at the most-recently added one. If the event is consumed, then no more event handlers are fired.
  2. If the event is consumed, stop. Else, the next node up the path is found, and Jazz returns to step 1.

2.4.1 An Example: squiggleEventHandler

As an example of a event handler, we provide a squiggleEventHandler which allows for mouse interaction to draw squiggles on the desktop. A squiggle is drawn on the desktop by clicking the mouse button and then dragging the mouse. The squiggle stops when the mouse button is released. Thus, the squiggleEventHandler needs provide the following functionality:

2.4.2. Sample Code

import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;

import edu.umd.cs.jazz.*;
import edu.umd.cs.jazz.util.*;
import edu.umd.cs.jazz.event.*;
import edu.umd.cs.jazz.component.*;

public class SquiggleEventHandler implements ZEventHandler, ZMouseListener, ZMouseMotionListener {
    private boolean active = false;        // True when event handlers are attached to a node
    private ZNode   node = null;           // The node the event handlers are attached to

    private ZGroup  drawingLayer;          // The node under which to add the new squiggle
    private ZPolyline polyline;            // The polyline currently being drawn
    private Point2D pt;                    // A reusable point

    public SquiggleEventHandler(ZGroup drawingLayer, ZNode node) {
        this.drawingLayer = drawingLayer;
        this.node = node;
        pt = new Point2D.Float();
    }
    
    /**
     * Specifies whether this event handler is active or not.
     * @param active True to make this event handler active
     */
    public void setActive(boolean active) {
        if (this.active && !active) {
                                // Turn off event handlers
            this.active = false;
            node.removeMouseListener(this);
            node.removeMouseMotionListener(this);
        } else if (!this.active && active) {
                                // Turn on event handlers
            this.active = true;
            node.addMouseListener(this);
            node.addMouseMotionListener(this);
        }
    }

    public boolean isActive() {
        return active;
    }

    public void mousePressed(ZMouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {   // Left button only
            ZSceneGraphPath path = e.getPath();
            ZCamera camera = path.getTopCamera();
            
            pt.setLocation(e.getX(), e.getY());
            path.screenToGlobal(pt);
            
            polyline = new ZPolyline(pt);
            ZVisualLeaf leaf = new ZVisualLeaf(polyline);

            polyline.setPenWidth(5.0f);
            polyline.setPenColor(Color.red);
            drawingLayer.addChild(leaf);
        }
    }
    
    public void mouseDragged(ZMouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {   // Left button only
            ZSceneGraphPath path = e.getPath();
            pt.setLocation(e.getX(), e.getY());
            path.screenToGlobal(pt);
            
            polyline.add(pt);
        }
    }
    
    public void mouseReleased(ZMouseEvent e) {
        if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) {   // Left button only
            polyline = null;
        }
    }

    /**
     * Invoked when the mouse enters a component.
     */
    public void mouseEntered(ZMouseEvent e) {
    }

    /**
     * Invoked when the mouse exits a component.
     */
    public void mouseExited(ZMouseEvent e) {
    }

    /**
     * Invoked when the mouse has been clicked on a component.
     */
    public void mouseClicked(ZMouseEvent e) {
    }

    /**
     * Invoked when the mouse button has been moved on a node
     * (with no buttons no down).
     */
    public void mouseMoved(ZMouseEvent e) {
    }
}

2.5. Jazz and Threads

Jazz is not thread-safe. Like Swing, Jazz has been designed from the start to run only in a single thread (usually the event dispatch thread). This constrains the way Jazz applications can be built, but we feel that overall, it increases usability by simplifying code, and eliminating thread-debugging issues.

An excellent starting point for learning about threads is to read what Sun has written about Swing and threads. Almost all of their solutions for multi-threaded code work for Jazz as well. Here are links to three major articles. Threads and Swing, Using a SwingWorker Thread, and The Last Word in Swing Threads.

One specific issue raised in these articles that often causes people problems is that the method

    public static void main(String[] args)
used to start a java program, is called from a thread that is not the primary event dispatch thread.  As a result, once events begin accessing the Jazz scenegraph (ie. when the ZCanvas has been made visible), this main thread (as well as any other) should not modify the scenegraph.  This includes animation, changing transforms, adding or deleting nodes, etc.

There are a few good reasons where it may be appropriate to run some code in a separate thread, such as with asynchronous animation. The following code will zoom in while the rest of the application is still active, and responds to events. The trick is that scaling the camera is always called in the primary Swing event dispatch thread.

import javax.swing.SwingUtilities;
import edu.umd.cs.jazz.*;

public class AnimTest {
    private boolean zooming = false;               // True during animated zooming
    private ZCamera camera = null;                 // Caller must specify camera animation should occur within

    public AnimTest(ZCamera camera) {
        this.camera = camera;
    }

    // Start the animation
    public void startZooming() {
        zooming = true;
        zoomOneStep();
    }

    // Stop the animation
    public void stopZooming() {
        zooming = false;
    }

    // This gets called to zoom one step
    public void zoomOneStep() {
        if (zooming) {
            camera.scale(1.1f, 0.0f, 0.0f);

            try {
                                // The sleep here is necessary.  Otherwise, there won't be
                                // time for the primary event thread to get and respond to
                                // input events.
                Thread.sleep(20);

                                // If the sleep was interrupted, then cancel the zooming,
                                // so don't do the next zooming step
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        AnimTest.this.zoomOneStep();
                    }
                });
            } catch (InterruptedException e) {
                zooming = false;
            }
        }
    }
}
This is a summary of the reasons we chose to build Jazz to run within a single thread: The primary disadvantage is that Jazz will not be directly speeded up when run on machines with multiple processors. 

2.6. Swing and Jazz

Clearly, Jazz is not independent from Swing. In fact, Jazz functions as a Swing widget itself through ZCanvas where it displays a Jazz ZDrawingSurface. However, Swing widgets themselves can also be embedded within Jazz through ZSwing. ZSwing acts as a Jazz Visual Component wrapper for the Swing widget to ensure its proper rendering, focusing, and bounds management within Jazz.

One Swing widget that requires special attention in Jazz is the JPopupMenu. Unfortunately, this also affects JMenu and JComboBox as these both maintain an instance of JPopupMenu. The fundamental problem with JPopupMenu is that it is always potentially heavyweight (for instance, the popup can extend outside the bounds of its parent window). Consequently, care must be excercised when using a JPopupMenu in Jazz. A JPopupMenu should likely be managed at the level of the ZCanvas rather than at the level of embedded Swing widgets. JMenu and JComboBox should also not be used with ZSwing. Instead, use ZMenu and ZComboBox exactly as you would their Swing counterparts, then add them to a ZSwing

2.7. Saving Jazz Scenegraphs

Jazz supports two mechanisms for saving scenegraphs. Jazz classes support standard Java Serialization which allows a class, or an entire scenegraph to be written to disk in a compact binary format. This is the recommended way to save Jazz scenegraphs. However, the risk with this format is that as Jazz is still under active development, we expect that until release 1.0, the Jazz internal structure will continue to change, and Serialized files will not be readable with future versions of Jazz.

To address this problem of incompatible file formats, Jazz also supports a custom file format which is more (although not completely) version resistant. It is a more bulky and also text-based file format. We expect that after Jazz stabilizes, we may introduce an XML based file format, and will eliminate this special format. This special file format is directly analogous to Java Serialization. Jazz classes implement the ZSerializable method and has similar readObject and writeObject methods that can be used just like Serializable objects.

Note that one difference between Java Serialization and Jazz ZSerialization is that Serialization follows all internal references, and thus if you try to write out any single node, all the pointers will be followed, and it will end up writing out the entire scenegraph including the cameras. ZSerialization, on the other hand, does not write out "up" pointers, and so if you write out a node with ZSerialization, it will only write out the sub-tree rooted at the node you specify. If you want to accomplish this functionality using Serialization, your best bet is to disconnect that subtree from the rest of the scenegraph with "getParent().removeChild(this);", write it out, and then add the subtree back.

Unlike Java Serialization, Jazz ZSerialization is not completely automated. In order to make a class ZSerializable, the class must implement the ZSerializable. interface, and it must write out and read the slots it cares about. It does not automatically write out non-transient slots like Serialization does. In addition, A ZSerializable must have a public no-arg constructor, or it will generate a ClassCastException when being read back in. 

2.8. Jazz Class Structure

The best way to learn the details of Jazz is look at the API docs. However, is a summary of the Jazz class structure that may be helpful in understanding all the pieces in one overview. The following figure shows the Jazz scenegraph class structure. Then the following sections discuss each node type, and the functionality they provide.


The class hierarchy of the Jazz scenegraph objects.

Nodes are of two basic types: leaves and groups. A leaf node is one that has no children. A group node has children. Some leaves (ZVisualLeaf) and some groups (ZVisualGroup) can render visual components. Other node types do not cause any actual rendering, but can provide functionality in other ways. For instance, a ZTransformGroup modifies the transform for all of its children. Let us look at each node type. 

2.8.1. ZRoot

Every scenegraph begins with a ZRoot which serves as the root of the entire scenegraph tree. Each scenegraph has exactly one root. 

2.8.2. ZLeaf

ZLeaf serves as a tag, identifying all sub-classes as being leaves. ZLeaf provides no other function, and there is no reason to instantiate a ZLeaf object. 

2.8.3. ZVisualLeaf

ZVisualLeaf is a leaf node that has one or more visual components that can be rendered. Many applications will attach all of their visual components to ZVisualLeafs.

2.8.4. ZGroup

ZGroup is a node with children. Applications may use ZGroup to "group" children. By inserting a group node above several children, the group node can then be manipulated which will affect all of its children. Groups are typically used when several objects should be treated as a semantic unit. 

2.8.5. ZFadeGroup

ZFadeGroup is a group node that controls transparency and fading of its sub-tree. Inserting a fade node into the tree lets you control transparency and minimum/maximum magnification of all its descendants. If this node is rendered below its minimum magnification, or above its maximum magnification, it and its children will not be rendered. The node and its subtree will be smoothly faded out as the minimum or maximum magnification is approached. 

2.8.6. ZLayerGroup

ZLayerGroup is used exclusively to specify the portion of the scenegraph that a camera can see. It has no other function. See ZCamera for more detail. 

2.8.7. ZAnchorGroup

ZAnchorGroup holds the information for a spatial hyperlink. An anchor represents a hyperlink from its children to an internally specified destination. Anchors can link to either another node, or to a bounds. There is an associated event handler ZLinkEventHandler that provides interaction for specifying these links. 

2.8.8. ZInvisibleGroup

ZInvisibleGroup makes all the nodes below it completely invisible. This can be inserted into a scenegraph when a portion of the tree needs to be temporarily hidden. 

2.8.9. ZVisualGroup

ZVisualGroup is a group node with visual components. It has two visual components (either or both of which could be null) which get rendered before and after the node's children, respectively. 

2.8.10. ZSelectionGroup

ZSelectionGroup is a visual group that provides functionality for specifying selection. Inserting a selection group in the scenegraph will visually select its subtree. It has utility methods for selecting and unselecting nodes. It manages a visual component that actually represents the selection. This class could be extended to replace the visual component if an application wants to define a different visual look. 

2.8.11. ZLayoutGroup

ZLayoutGroup is a visual group that wraps a layout manager that can position the node's children. The layout manager may also include a visual component that aids the layout. For instance, the tree layout manager adds links connecting the tree nodes. 

2.8.12. ZTransformGroup

ZTransformGroup is a group node that specifies an arbitrary affine transform. This provides the capability to translate, scale, rotate, or shear the subtree of this node. 

2.8.13. ZConstraintGroup

ZConstraintGroup is a transform group that changes its transform based on a computation defined in a specified method. Every time the camera view is changed, the method is called, recomputing the transform. Thus, depending on the algorithm chosen, various dynamic behaviors can be created. See ZStickyGroup. 

2.8.14. ZStickyGroup

ZStickyGroup is a constraint group that moves its children inversely to the camera view, so that the children stay visually on the same place on the screen, even as the camera view changes. The sticky node has a variation called sticky "Z" which allows its children to pan with camera changes, but not zoom. 

2.8.15. ZDrawingSurface

ZDrawingSurface represents the thing the camera renders onto. Typically, a drawing surface will be associated with a ZCanvas window. However, a drawing surface can also represent a printer, and thus rendering to a window and printer is implemented in the same way. The drawing surface is associated with a window or printer by specifying which Graphics2D to render onto.

2.8.16. ZNameGroup

ZNameGroup is a group node that names a portion of the scenegraph. A name group node can be inserted into the tree when an application wants to assign a name to that section of the scenegraph. A static method ZNameGroup.getNameGroup(String name) returns the name group node associated with the specified name.

2.8.17. ZSpatialIndexGroup

ZSpatialIndexGroup is a group node that supports R-tree indexing for a group of visual components. This indexing can provide faster rendering when a large number of visual components are being displayed. Currently, significant speed improvements become apparent when a few thousand or more nodes are being rendered. Using a ZSpatialIndexGroup group node with a small number of nodes can actually slow down rendering, due to the indexing overhead.

2.8.19. ZClipGroup

ZClipGroup is a group node that clips the current rendering context with a ZShape before rendering its children. It can also optionally use the ZShape when rendering and picking itself.