Development Notes: Shape Sets

Updated for release version: 1.0.0-alpha

This page discusses shape sets in modelGUI (classes which inherit ShapeSet). It provides a summary of how sets are conceptually designed, and discusses implementation details which are important to know when working with, or extending, shape set functionality.

Introduction

A Shape Set, as the name suggests, provides an interface for representing a set of shapes. Shape sets can contain other shape sets, which allows a hierarchical tree organization scheme. All shape sets in a model are contained in a single base shape set, associated with a shape model. There are two main types of shape set in mgui: ShapeSet3DInt, and ShapeSet2DInt, which inherit (and contain instances of) Shape3DInt and Shape2DInt, respectively. This means that they provide methods for rendering themselves (in this case, their members), providing a list of nodes, calculating bounds, setting tree nodes, firing shape events, and listening to attribute changes. In addition, they specify methods for adding, removing, or moving shapes, as well as a number of other functions such as returning a list filtered for a specific shape type. The following describes the implementation details of these sets.

Attributes

Attribute Listeners

Tree Node

The shapeUpdated method for ShapeTreeNode looks like:

/***************************************
 * Respond to a shape event on this node's ShapeInt. The current implementation only responds to updates on a
 * {@link ShapeSet}, but adding, removing, or moving child nodes depending on the nature of the event. 
 * 
 * @param e
 */
@Override
public void shapeUpdated(ShapeEvent e){
    ShapeSet set = null;
    InterfaceShape shape = null;
    switch (e.eventType){
        case ShapeAdded:
            if (!(e.getShape() instanceof ShapeSet)) return;
            set = (ShapeSet)e.getShape();
            if (set.getLastAdded() == null) return;
            //add shape's tree node to this tree 
            shape = set.getLastAdded();
            addChild(shape.getTreeNode());
            return;
 
        case ShapeRemoved:
            if (!(e.getShape() instanceof ShapeSet)) return;
            set = (ShapeSet)e.getShape();
            if (set.getLastRemoved() == null) return;
            //remove shape's tree node from this tree 
            shape = set.getLastRemoved();
            removeShapeNode(shape);
            return;
 
        case ShapeInserted:
        case ShapeMoved:
            //set tree node from scratch
            this.getShape().setTreeNode(this);
            //attempt to re-expand node path
            JTree tree = this.getParentTree();
            if (tree != null)
                tree.scrollPathToVisible(new TreePath(((DefaultMutableTreeNode)this.children.get(0)).getPath()));
            return;
 
        }
}

Updating the Set

A Shape set, like a ShapeInt, keeps track of its bounds and center point, which is preferable to calculating these values multiple times, on the fly. This update is performed by the method updateShape, which essentially calculates a union of all its member bounds. updateShape assumes that all of the set's members have already themselves been updated, so it does not call an update on each of them.

Shape Listeners

A shape set registers itself as a ShapeListener on all its members, and removes itself if a member is removed. This allows the set to respond to changes in its members, for instance to update its bounds and center point information. For ShapeSet3DInt this implementation looks like:

/************************************************
 * Responds to a change in a member shape. Typically this involves calling {@link updateShape} to update
 * this set's bounds and center point.
 * 
 * @param e a <code>ShapeEvent</code> characterizing the change
 */
@Override
public void shapeUpdated(ShapeEvent e){
 
    if (e.alreadyResponded(this)) return;
    e.responded(this);
 
    switch (e.eventType){
        case ShapeAdded:
            updateShape();
            fireShapeModified();
            return;
 
        case ShapeModified:
            updateShape();
            if (e.modifiesShapeSet())
                fireShapeModified();
            else
                fireShapeListeners(e);
            return;
 
        case AttributeModified:
            updateShape();
            return;
 
        case ShapeDestroyed:
            updateShape();
            return;
 
        }
}

Adding Shapes

Shapes are added via one of the multiple implementations of the addShape method, or via the addShapes method, to add lists of shapes. addShape performs a number of tasks:

  • Add the shape to the list of members.
  • Update the shape by calling shape.updateShape.
  • Update the set itself to reflect the addition of new shape.
  • Set this set as the parent set for shape by calling shape.setParentSet. This also removes shape from its existing parent set if applicable.
  • Register this set's camera listeners with the new shape.
  • Set the shape's Java3D node by calling shape.setScene3DObject, and add it to the set's Java3D node using shape.getShapeSceneNode.
  • Fire a ShapeAdded ShapeEvent to all registered shape listeners.

The update events can be avoided by passing false for the updateShape argument, and the event firing can be avoided by passing false for the fireListeners argument. This is only recommended, however, if you are using the set to perform non-GUI-related tasks.

The implementation of addShape for ShapeSet3DInt looks like:

/****************************************************
 * Adds <code>shape</code> to this shape set. If <code>updateShape</code> is <code>true</code>, performs updates on the
 * shape, sets this set as its parent set and registers itself as a shape listener on <code>shape</code>, registers 
 * camera listeners, and generates a Java3D node. If <code>updateListeners</code> is <code>true</code>, fires this shape 
 * set's listeners with a <code>ShapeAdded ShapeEvent</code>.
 * 
 * <p>It is not recommended to set these arguments to <code>false</code> unless you are using this set to perform
 * non-GUI-related tasks.
 * 
 * @param shape The shape to add
 * @param index The index at which to insert the shape
 * @param updateShape Specifies whether to perform shape updates
 * @param updateListeners Specifies whether to fire shape listeners
 * @return <code>true</code> if successful 
 */
public boolean addShape(Shape3DInt shape, int index, boolean updateShape, boolean updateListeners){
 
    if (shape instanceof ShapeSet3DInt && isAncestorSet((ShapeSet3DInt)shape)) return false;
    if (shape.equals(this)) return false;
 
    if (index < 0)
        members.add(shape);
    else
        members.add(index, shape);
 
    if (updateShape){
        //shape bounds update
        shape.updateShape();
        updateShape();
 
        //set this as parent (will remove it from an existing parent, and add this as a shape listener)
        shape.setParentSet(this);
 
        //register camera listeners
        if (shape instanceof ShapeSet3DInt){
            ((ShapeSet3DInt)shape).registerCameras(registered_cameras);
        } else if (shape.hasCameraListener) {
            for (int i = 0; i < registered_cameras.size(); i++)
                shape.registerCameraListener(registered_cameras.get(i));
            }
 
        //set model
        if (getModel() != null)
            shape.setID(getModel().getNextID());
 
        //set shape's scene node
        shape.setScene3DObject();
 
        //set this set's scene node
        if (scene3DObject == null){
            setScene3DObject();
        }else{
            ShapeSceneNode node = shape.getShapeSceneNode();
            if (node.getParent() != null)
                node.detach();
            try{
                scene3DObject.addChild(node);
            }catch (Exception e){
                node.detach();
                scene3DObject.addChild(node);
                }
            }
        }
 
    //alert listeners; this includes any tree nodes
    if (updateListeners){
        last_added = shape;
        ShapeEvent e = new ShapeEvent(this, ShapeEvent.EventType.ShapeAdded);
        fireShapeListeners(e);
        last_added = null;
        }
 
    return true;
 
}

Removing Shapes

ShapeInts are removed from a shape set using the removeShape method.

Modifying Shapes

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License