/* ========================
* 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-2004, by :
* Corporate:
* EADS Corporate Research Center
* Individual:
* Nicolas Brodu
*
* $Id: SceneNode.java,v 1.14 2006/11/23 15:50:31 ogor Exp $
*
* Changes
* -------
* 19-Mar-2004 : Creation date (NB);
*
*/
package syn3d.nodes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.Icon;
import simtools.data.EndNotificationListener;
import simtools.util.ListenerManager;
import syn3d.base.ActiveNode;
import syn3d.base.PluginManager;
/**
*
*/
public class SceneNode extends GroupNode implements EndNotificationListener, Serializable {
static final long serialVersionUID = 7783556837479668060L;
/**
* In rotation mode, the user rotates the whole scene and can zoom in or out.
* Rotation mode is adapted to study one object. This is the default
*/
public static final int ROTATION_MODE = 0;
/**
* In fly-by mode, the user can freely move in the scene. This is more adapted for
* scenes with multiple objects or for objects with a complex internal geometry to study.
*/
public static final int FLY_BY_MODE = 1;
protected int viewingMode;// = FLY_BY_MODE;
/**
* @return Returns the viewingMode.
*/
public int getViewingMode() {
return viewingMode;
}
/**
* @param viewingMode The viewingMode to set.
*/
public void setViewingMode(int viewingMode) {
this.viewingMode = viewingMode;
}
/**
* Current file name used for saving the scene
*/
protected String fileName = null;
/** An array of UniverseChangeListener objects */
protected transient ListenerManager listeners = 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
*/
protected transient ListenerManager dirtyNodes = new ListenerManager(); // Use ListenerManager too
protected static int anonymousSceneNumber = 0;
/**
* This timer is used to gather notify events and send them at a reasonable rate to 3D motors.
*/
protected transient Timer timer;
/**
* True when the user has changed the period for refreshing the scene>
*/
protected boolean refreshPeriodChanged = false;
static public int REFRESH_PERIOD = 100;
protected long refreshPeriod = REFRESH_PERIOD;
public SceneNode(ActiveNode parent) {
super(parent);
if (!(parent instanceof RootNode)) return;
anonymousSceneNumber++;
name = NodeResourcesManager.getNodeName("Scene") + ((anonymousSceneNumber == 1) ? "" : String.valueOf(anonymousSceneNumber));
}
/**
* Restore scene : subclasses can restore the scene once all ActiveNOde have been initialised
*/
public void restoreScene(PluginManager pm){
}
/**
* Use this function to get the scene containing this node
* @param node A node whose scene is unknown
* @return the scene in which this node resides, or null if this node is not attached to a scene
*/
public static SceneNode getScene(ActiveNode node) {
while (node != null) {
if (node instanceof SceneNode) return (SceneNode)node;
node = node.getParent();
}
return null;
}
/* (non-Javadoc)
* @see syn3d.base.ActiveNode#getActions()
*/
public List getActions() {
ArrayList list = new ArrayList();
if (isVisible())
list.add(NodeResourcesManager.getResources().getString("Close"));
else
list.add(NodeResourcesManager.getResources().getString("Show"));
if (getViewingMode()== SceneNode.ROTATION_MODE) {
list.add("Fly-by viewing mode");
} else
list.add("Rotation viewing mode");
//list.add(NodeResourcesManager.getResources().getString("Properties"));
return list;
}
/* (non-Javadoc)
* @see syn3d.base.ActiveNode#doAction(java.lang.String)
*/
public void doAction(Object action) {
if (action==null) return;
if (action.equals(NodeResourcesManager.getResources().getString("Close"))) {
setVisible(false);
}
if (action.equals(NodeResourcesManager.getResources().getString("Show"))) {
setVisible(true);
}
if (action.equals("Fly-by viewing mode")) {
setViewingMode(SceneNode.FLY_BY_MODE);
}
if (action.equals("Rotation viewing mode")) {
setViewingMode(SceneNode.ROTATION_MODE);
}
if (action.equals(NodeResourcesManager.getResources().getString("Properties"))) {
// TODO : light properties, (ambient light and directional)
// TODO add lights
}
}
public boolean isVisible() {
return false;
}
public void setVisible(boolean status) {
// For subclasses
}
public void setRefreshPeriod(long period){
if ((period!=refreshPeriod) && (period>0)){
refreshPeriodChanged = true;
refreshPeriod = period;
}
}
public long getRefreshPeriod(){
return refreshPeriod;
}
protected static Icon icon = NodeResourcesManager.resources.getIcon("sceneIcon");
public Icon getIcon() {
return icon;
}
public void restoreReferences(ActiveNode parent) {
super.restoreReferences(parent);
if (!(parent instanceof RootNode)) return;
}
public boolean saveChildren() {
return super.saveChildren();
}
/**
* Register the scene as the unique listener for 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(SceneNode.this) {
notifyListeners();
}
}
}
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);
}
}
/**
* Removes this node from the parent list. Notifies the parents for structural change.
*/
public void remove() {
super.remove();
if (timer!=null)
timer.cancel();
}
// Listeners related functions. Take care of duplicates
public void addListener(SceneChangeListener dsl) {
listeners.add(dsl);
}
public void removeListener(SceneChangeListener dsl) {
listeners.remove(dsl);
}
// 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, to notify all registered listeners */
public void notifyListeners() {
// Do not even bother to cleanup dirty nodes if there is noone to see the changes
if (listeners.size() == 0) return;
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();
}
}
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
SceneChangeListener ucl = (SceneChangeListener)listeners.get(i);
if (ucl!=null) ucl.sceneChanged(this);
}
}
}
/**
* Receive change notification events for objects in this scene and propagate them
* to this scene listeners.
* Don't propagate the event to the root node, as it doesn't care for it.
*/
protected void propagateInternalChangeEvent(ActiveNode node) {
notifyListeners();
}
/**
* Catch highlight events as this can cause repaint.
* This is just a hook so the Root can notify its own listeners
*/
protected void propagateHighlightEvent(ActiveNode node, boolean on) {
notifyListeners();
super.propagateHighlightEvent(node,on);
}
/**
* Initialize 2D rotation algorithm with the current position as origin.
* Typically, this is related to mouse positions in X and Y.
*/
public void init2DPosition(int posX, int posY) {
}
/**
* Does a rotation of the scene according to moves in a 2D coordinate system.
* Use init2DPosition to position an origin, then do as many rotate2D as required.
* @param newX The new X position in 2D, typically a mouse position
* @param newY The new Y position in 2D, typically a mouse position
*/
public void rotate2D(int newX, int newY) {
}
/**
* Does a translation of the scene according to moves in a 2D coordinate system.
* Use init2DPosition to position an origin, then do as many translate2D as required.
* @param newX The new X position in 2D, typically a mouse position
* @param newY The new Y position in 2D, typically a mouse position
*/
public void translate2D(int newX, int newY) {
}
/**
* Uses the 2D increments in position to compute a zoom factor, then zooms the scene accordingly.
* @param newX The new X position in 2D, typically a mouse position
* @param newY The new Y position in 2D, typically a mouse position
*/
public void zoom2D(int newX, int newY) {
}
/**
* Zooms in or out of the scene according to the increment. This is an arbitrary algorithm
* to help create correct zoom factors. You could use setZoomFactor and then update
* the zoom matrix directly, with the same effect.<br>
* The added value of this function is to provide a relative and easy to manipulate
* way to zoom in or out, with increments like +1 or -1 for small zooms, and +10 and -10
* for greater zooms, etc...<br>
* @param zoomIncrement A value typically 1 or -1, but which can be greater for fast zooms.
* Positive values zoom in, negative values zoom out.
*/
public void zoom(int zoomIncrement) {
}
/**
* Turns perspective on an off
*/
public void changeProjection() {
}
/** Reset all values to default */
public void reset() {
}
/** Auto zooms the scene out of all objects */
public void autoZoom() {
}
/**
* Adds or removes a single pick at the given position to the selected objects.
* @param posX the 2D X position where to do the picking
* @param posY the 2D Y position where to do the picking
* @return Returns the current selection, possibly an empy array
* @see ActiveNode.higlight(boolean,Object)
*/
public ArrayList toggleSinglePick(int posX, int posY) {
return new ArrayList();
}
/**
* Adds or removes all picks between the given position and the last position,
* to the selected objects.
* @param posX the 2D X position defining a region with the last position. All objects in this region should be picked.
* @param posX the 2D Y position defining a region with the last position. All objects in this region should be picked.
* @return Returns the current selection, possibly an empy array
* @see ActiveNode.higlight(boolean,Object)
*/
public ArrayList toggleAllPicks(int posX, int posY) {
return new ArrayList();
}
/**
* Selects a single pick at the given position.
* @param posX the 2D X position where to do the picking
* @param posY the 2D Y position where to do the picking
* @return Returns the picked node or possibly a null object if there was nothing to pick at this position
* @see ActiveNode.higlight(boolean,Object)
*/
public ActiveNode pick(int posX, int posY) {
return null;
}
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
listeners = new ListenerManager();
dirtyNodes = new ListenerManager();
}
}