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.