/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2007, by :
* Corporate:
* EADS Astrium
* Individual:
* Claude Cazenave
*
* $Id: Universe.java,v 1.9 2008/12/17 22:37:53 cazenave Exp $
*
* Changes
* -------
* 4 janv. 08 : Initial public release
*
*/
package jsynoptic.plugins.java3d;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Group;
import javax.media.j3d.Locale;
import javax.media.j3d.Node;
import javax.media.j3d.SceneGraphObject;
import javax.media.j3d.View;
import javax.media.j3d.VirtualUniverse;
import simtools.data.EndNotificationListener;
import simtools.util.ListenerManager;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.scenegraph.io.SceneGraphFileReader;
import com.sun.j3d.utils.scenegraph.io.SceneGraphFileWriter;
import com.sun.j3d.utils.scenegraph.io.UnsupportedUniverseException;
import com.sun.j3d.utils.universe.Viewer;
import com.sun.j3d.utils.universe.ViewingPlatform;
public class Universe extends VirtualUniverse implements EndNotificationListener {
/** One locale is enough but easy to extend if needed */
Locale _rootLocale;
/** a map with all the viewers for each canvas */
HashMap<Canvas3D, Viewer> _viewers;
/** a list of listener */
ArrayList<Listener> _listeners;
/** An array of DisplayListener objects */
ListenerManager _displayListeners = new ListenerManager();
/**
* An array of Dirty nodes to update before calling listeners
* The dirty nodes are then automatically removed from this array, as they're supposed to be cleaned up
*/
ListenerManager _dirtyNodes = new ListenerManager();
/**
* True when the user has changed the period for refreshing the scene>
*/
boolean _refreshPeriodChanged = false;
static public int REFRESH_PERIOD = 100;
long _refreshPeriod = REFRESH_PERIOD;
/**
* This timer is used to gather notify events and send them at a reasonable rate to 3D motors.
*/
Timer _timer;
Universe() {
_rootLocale = new Locale(this);
_viewers = new HashMap<Canvas3D, Viewer>();
_listeners = new ArrayList<Listener>();
}
public Locale getLocale() {
return _rootLocale;
}
public File getFile() {
return UniversePool.getGlobal().getFile(this);
}
public String getName() {
String name = getFile().getName();
int k = name.lastIndexOf(UniversePool.JAVA_3D_FILE_EXT);
if (k > 0) {
name = name.substring(0, k);
}
return name;
}
public void addBranchGraph(BranchGroup bg) {
bg.setCapability(BranchGroup.ALLOW_DETACH);
_rootLocale.addBranchGraph(bg);
}
public void addView(NodeSelector selector) {
new Java3dFrame(getName(), -1, -1, -1, -1, this, selector);
}
ViewingPlatform getViewingPlatform(Canvas3D canvas) {
Viewer v = _viewers.get(canvas);
if (v != null) {
return v.getViewingPlatform();
}
return null;
}
Viewer getViewer(Canvas3D canvas) {
return _viewers.get(canvas);
}
public Set<Canvas3D> getCanvas() {
return _viewers.keySet();
}
void addCanvas(Canvas3D canvas) {
// check if one exists
if (_viewers.containsKey(canvas)) {
throw new IllegalArgumentException("canvas already exists");
}
Viewer viewer = new Viewer(canvas);
viewer.getView().setMinimumFrameCycleTime(30);
ViewingPlatform viewingPlatform = new ViewingPlatform(1);
viewingPlatform.setNominalViewingTransform();
_rootLocale.addBranchGraph(viewingPlatform);
viewer.setViewingPlatform(viewingPlatform);
_viewers.put(canvas, viewer);
for (Listener l : _listeners) {
l.notifyChange();
}
}
void removeCanvas(Canvas3D canvas) {
Viewer v = _viewers.get(canvas);
// check if one exists
if (v == null) {
return;
}
cleanupViewer(v);
_viewers.remove(canvas);
for (Listener l : _listeners) {
l.notifyChange();
}
}
private void cleanupViewer(Viewer v) {
// Get view associated with this SimpleUniverse
View view = v.getView();
// cleanup all off-screen canvases
for (int i = view.numCanvas3Ds() - 1; i >= 0; i--) {
Canvas3D c = view.getCanvas3D(i);
if (c.isOffScreen()) {
c.setOffScreenBuffer(null);
}
}
// Remove all canvases from view; remove the viewing platform from
// this viewer; remove all locales to cleanup the scene graph
view.removeAllCanvas3Ds();
_rootLocale.removeBranchGraph(v.getViewingPlatform());
v.setViewingPlatform(null);
}
public ArrayList<BranchGroup> getSceneGraphs() {
Enumeration<?> e = _rootLocale.getAllBranchGraphs();
ArrayList<BranchGroup> res = new ArrayList<BranchGroup>();
while (e.hasMoreElements()) {
BranchGroup bg = (BranchGroup) e.nextElement();
if (!(bg instanceof ViewingPlatform)) {
res.add(bg);
}
}
return res;
}
/**
* Register the universe as the unique listener for animators data events in the whole scene graph.
* This way, all events coming from the same source are fusionned into a single event
* for the scene
*/
public void notificationEnd(Object referer) {
notifyChange();
}
/**
* Notify listeners that this scene has changed.
* For performance purposes, all notification changes are coalesced into a single event
* no more than the scene period notification delay.
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
protected class SceneTimerTask extends TimerTask{
public void run() {
synchronized(Universe.this) {
updateDirtyNodes();
}
}
}
protected synchronized void notifyChange() {
// if there is a timer and no change about the refreshperiod, let it do its job
if (_refreshPeriodChanged || _timer == null){
_refreshPeriodChanged = false;
if (_timer!=null)
_timer.cancel();
_timer = new Timer(true); // do not block the application on exit
_timer.schedule(new SceneTimerTask(),0,_refreshPeriod);
}
}
// Dirty Nodes handling. Idem
public void addDirtyNode(DirtyNode node) {
_dirtyNodes.add(node);
}
public void removeDirtyNode(DirtyNode node) {
_dirtyNodes.remove(node);
}
/** Call this when the scene changed */
public void updateDirtyNodes() {
if (_dirtyNodes.size()>0) {
synchronized(_dirtyNodes) {
int n = _dirtyNodes.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
DirtyNode dn = (DirtyNode)_dirtyNodes.get(i);
if (dn!=null) dn.cleanup();
}
// Nodes are clean now.
_dirtyNodes.clear();
}
}
}
void read(File f) throws IOException {
SceneGraphFileReader r = new SceneGraphFileReader(f);
BranchGroup[] bgs = r.readAllBranchGraphs();
for (BranchGroup bg : bgs) {
_rootLocale.addBranchGraph(bg);
}
}
void write(File f) throws IOException {
try {
SceneGraphFileWriter w = new SceneGraphFileWriter(f, null, false,
"TBDuinverseName", null);
Enumeration<?> e = _rootLocale.getAllBranchGraphs();
while (e.hasMoreElements()) {
BranchGroup bg = (BranchGroup) e.nextElement();
if (!(bg instanceof ViewingPlatform)) {
// TODO serialize a name ???
w.writeBranchGraph(bg, null);
}
}
w.close();
} catch (UnsupportedUniverseException e) {
// will not be thrown but in case
throw new IOException(e.getMessage());
}
}
/**
* Cleanup memory use and reference by SimpleUniverse. Typically it should
* be invoked by the applet's destroy method.
*/
public void cleanup() {
for (Viewer v : _viewers.values()) {
cleanupViewer(v);
}
removeAllLocales();
// viewerMap cleanup here to prevent memory leak problem.
Viewer.clearViewerMap();
Primitive.clearGeometryCache();
}
public ArrayList<Node> getNodes(Class<? extends Node> nodeClass){
ArrayList<Node> res=new ArrayList<Node>();
Enumeration<?> e=getAllLocales();
while(e.hasMoreElements()){
Locale l=(Locale)e.nextElement();
Enumeration<?> el=l.getAllBranchGraphs();
while(el.hasMoreElements()){
BranchGroup bg=(BranchGroup)el.nextElement();
if(nodeClass.isAssignableFrom(bg.getClass())){
res.add(bg);
}
getNodes(res, nodeClass, bg);
}
}
return res;
}
private static void getNodes(ArrayList<Node> res, Class<? extends Node> nodeClass, Group g){
Enumeration<?> e=g.getAllChildren();
while(e.hasMoreElements()){
Node n=(Node)e.nextElement();
if(nodeClass.isAssignableFrom(n.getClass())){
res.add(n);
}
if(n instanceof Group){
getNodes(res,nodeClass, (Group)n);
}
}
}
public void addListner(Listener l) {
_listeners.add(l);
}
public void removeListner(Listener l) {
_listeners.remove(l);
}
public interface Listener {
public void notifyChange();
}
public static SceneGraphObjectHolder findHolder(SceneGraphObjectHolder start, SceneGraphObject object){
ArrayList<SceneGraphObjectHolder> roots=new ArrayList<SceneGraphObjectHolder>();
start.getRootHolders(roots);
return findHolder(roots, object);
}
private static SceneGraphObjectHolder findHolder(ArrayList<SceneGraphObjectHolder> list, SceneGraphObject object){
for(SceneGraphObjectHolder s : list){
if(s.getSceneGraphObject()==object){
return s;
}
ArrayList<SceneGraphObjectHolder> c=new ArrayList<SceneGraphObjectHolder>();
s.getChildrenHolders(c);
SceneGraphObjectHolder h=findHolder(c, object);
if(h!=null){
return h;
}
}
return null;
}
public static Universe getGraphPath(Node n, SceneGraphObject o,
ArrayList<SceneGraphObject> path) {
if(n==null){
if(o instanceof Node){
n=(Node)o;
}
else{
path.add(o);
return null;
}
}
if(o!=null && o!=n){
path.add(n);
}
Node np = n.getParent();
while (np != null) {
path.add(np);
np = np.getParent();
}
if (n.getLocale() == null) {
return null; // detached
}
return (Universe) (n.getLocale().getVirtualUniverse());
}
}