/* Copyright 2006 by Sean Luke and George Mason University Licensed under the Academic Free License version 3.0 See the file "LICENSE" for more information */ package sim.portrayal3d; import sim.util.*; import sim.field.*; import sim.portrayal.*; import java.util.*; import javax.media.j3d.*; import javax.vecmath.*; /** * An abstract superclass for all FieldPortrayal3Ds which display SparseFields. * This class handles the createModel() and updateModel() methods for you; all you * need to implement are the setField(), completedWrapper(), and getLocationOfObjectAsVector3d() * methods. * * <p>SparseFieldPortrayal3D presently takes the TransformGroups of the models of its children and * wraps them into BranchGroups so that they can be removed and added dynamically. * * @author Gabriel Balan */ public abstract class SparseFieldPortrayal3D extends FieldPortrayal3D { /** Converts a given location (perhaps a Double3D, Double2D, Int3D, or Int2D) into a Vector3d, placing it in the given Vector3d, and returning that Vector3d. Double2D and Int2D should convert to a Vector3d with a zero Z value. */ public abstract Vector3d getLocationOfObjectAsVector3d(Object location, Vector3d putInHere); public TransformGroup createModel() { SparseField field = (SparseField)(this.field); Vector3d locationV3d = new Vector3d(); TransformGroup globalTG = new TransformGroup(); globalTG.setCapability(TransformGroup.ALLOW_CHILDREN_READ); globalTG.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE); globalTG.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND); if (field==null) return globalTG; Bag objects = field.getAllObjects(); Transform3D tmpLocalT = new Transform3D(); for(int z = 0; z<objects.numObjs; z++) { getLocationOfObjectAsVector3d(objects.objs[z], locationV3d); tmpLocalT.setTranslation(locationV3d); globalTG.addChild(wrapModelForNewObject(objects.objs[z], tmpLocalT)); } return globalTG; } /** * This function is called from createModel for each object in the * field and from the updateModel part of getModel for the * new objects. * * <p>In order to dynamically add/remove the subtrees associated with * children, this function wraps their TransformGroups into BranchGroups. **/ protected BranchGroup wrapModelForNewObject(Object o, Transform3D localT) { Portrayal p = getPortrayalForObject(o); if(! (p instanceof SimplePortrayal3D)) throw new RuntimeException("Unexpected Portrayal " + p + " for object " + o + " -- expecting a SimplePortrayal3D"); SimplePortrayal3D p3d = (SimplePortrayal3D)p; p3d.setCurrentFieldPortrayal(this); TransformGroup localTG = p3d.getModel(o, null); localTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); localTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); localTG.setTransform(localT); BranchGroup localBG = new BranchGroup(); localBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ); localBG.setCapability(BranchGroup.ALLOW_DETACH); localBG.addChild(localTG); localBG.setUserData(o); return localBG; } public void updateModel(TransformGroup globalTG) { SparseField field = (SparseField)(this.field); if (field==null) return; Bag b = field.getAllObjects(); HashMap hm = new HashMap(); Transform3D tmpLocalT = new Transform3D(); Vector3d locationV3d = new Vector3d(); // put all objects into hm for(int i=0;i<b.numObjs;i++) hm.put(b.objs[i],b.objs[i]); // update children if they're still in the field, // else remove the children if they appear to have left. // We use a hashmap to efficiently mark out the children // as we delete them and update them // build a Bag of children to remove Bag toRemove = new Bag(); // for each child in the array... for(int t = 0; t < globalTG.numChildren(); t++) { BranchGroup localBG = (BranchGroup)(globalTG.getChild(t)); // get the object represented by the child Object fieldObj = localBG.getUserData(); // try to remove the object from hm. Returns null if it wasn't there. if(hm.remove(fieldObj) != null) { // object still in the field. // Do an update on the child. // we can pull this off because sparse fields are not allowed to contain null -- Sean TransformGroup localTG = (TransformGroup)localBG.getChild(0); Portrayal p = getPortrayalForObject(fieldObj); if(! (p instanceof SimplePortrayal3D)) throw new RuntimeException("Unexpected Portrayal " + p + " for object " + fieldObj + " -- expecting a SimplePortrayal3D"); SimplePortrayal3D p3d = (SimplePortrayal3D)p; p3d.setCurrentFieldPortrayal(this); TransformGroup localTG2 = p3d.getModel(fieldObj, localTG); getLocationOfObjectAsVector3d(fieldObj, locationV3d); tmpLocalT.setTranslation(locationV3d); localTG2.setTransform(tmpLocalT); if(localTG != localTG2) { localTG2.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); localTG2.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); BranchGroup newlocalBG = new BranchGroup(); newlocalBG.setCapability(BranchGroup.ALLOW_CHILDREN_READ); newlocalBG.setCapability(BranchGroup.ALLOW_DETACH); newlocalBG.setUserData(fieldObj); newlocalBG.addChild(localTG2); globalTG.setChild(newlocalBG, t); } } else // object is no longer in the field -- remove it from the scenegraph toRemove.add(localBG); } // Now remove elements for(int i = 0; i < toRemove.numObjs; i++) // Ugh, this is truly awful globalTG.removeChild((Node)toRemove.objs[i]); // O(n), yuck yuck yuck. But we have to do this because Java3D has no efficient way around it. Even removeAllChildren just does a for-loop and removes each child in turn (O(n^2)!! Who are these dufuses?) // The remaining objects in hm must be new. We add them to the scenegraph. // But first, we should check to see if hm is empty. if (!hm.isEmpty()) { Iterator newObjs = hm.values().iterator(); // yuck, inefficient while(newObjs.hasNext()) { Object fieldObj = newObjs.next(); locationV3d = getLocationOfObjectAsVector3d(fieldObj, locationV3d); if (locationV3d != null) tmpLocalT.setTranslation(locationV3d); BranchGroup localBG = wrapModelForNewObject(fieldObj, tmpLocalT); globalTG.addChild(localBG); } } } }