/*
* Project Info: http://jcae.sourceforge.net
*
* 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 2008, by EADS France
*/
package org.jcae.vtk;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import vtk.vtkActor;
import vtk.vtkCellArray;
import vtk.vtkFloatArray;
import vtk.vtkPainterPolyDataMapper;
import vtk.vtkPointData;
import vtk.vtkPolyData;
import vtk.vtkPolyDataNormals;
import vtk.vtkProperty;
/**
* Nodes of scene graph.
* The aim of a viewer is of course to display graphical objects. But a scene is
* not static, view has to be refreshed when properties change. For instance
* when an object is selected, it is highlighted. Color or material may also
* be edited by user, and scene has to be rebuild quickly.
*
* With OpenGL, one can concatenate static objects into so-called display lists,
* and compile them so that rendering is very fast. But if an object changes,
* display list has to be rebuilt, which can be slow. This must be taken into
* account when designing a data structure, otherwise performance can become
* very poor.
*
* The same logic applies to VTK as well. A vtkActor is an object which will
* be compiled and can be rendered efficiently. Its geometry is defined by
* a vtkMapper, more exactly a vtkPolyDataMapper in our case. It takes some
* time to build OpenGL primitives from this geometry, but when this is done,
* rendering is very fast when camera moves or vtkActor is highlighted.
* It is also possible to change only a subset of properties associated to
* geometric data, to highlight only some cells, but this may also take some
* time if dataset is very large.
*
* VTK becomes too slow when there are many actors, but on the other hand
* interactive changes become also too slow if too many data are put into
* actors. We implemented a tree graph to help merging geometric entities
* into single actors. There are two types of nodes:
* <ul>
* <li>{@link Node}: containers, its children may be Node or LeafNode instances;</li>
* <li>{@link LeafNode}: leaf nodes contain geometric data.</li>
* </ul>
*
* This merge is performed only if the Node is set as managing (by using the
* {@link #setManager()} method). FIXME: a managing Node can currently contain
* either Node or LeafNode instances, but in practice it almost always contains
* LeafNode. If we make this a rule, recursive behavior of these nodes will
* be much easier to implement and more efficient too.
*
* This is a general framework, only application developers can know how to
* organize their geometrical objects into Node and LeafNode.
*
* Nodes can be highlighted using the {@link #select} method. A subset of
* their underlying geometry can be highlighted using the {@link #setCellSelection}
* method, which modifies {@link #selectionActor} actor.
*
* Nodes can be declared as being not pickable to speed up picking, since non
* pickable nodes are ignored.
*
* You can give customisers to the nodes. This works as follows: if a leaf node
* has no customiser it takes the first parent that have one and if nobody have
* customiser the DEFAULT customiser is taken. This permits to create a
* customiser for the parent node and this will be used for all of its children
* (unless if the child has a customiser). Customisers are created to permit
* to change and customise VTK objects easily. Actually only color can be
* specified for shading of the geometry. If you want to customise the nodes
* more you can use the VTK interface but if you merge the leaves in one node
* and they have different materials this will cause problems. The solution to
* this is that VTK permits to make materials data arrays like color array.
*
* The node by default take some characteristics of the parent node for example the pickability
* and the visibility.
* When applying a customiser the actor customisation is refreshed and if we are in a Node,
* all the children inherit it.
*
* @author Julian Ibarz
*/
public abstract class AbstractNode
{
private final static Logger LOGGER = Logger.getLogger(AbstractNode.class.getName());
/** Parent node */
protected final Node parent;
private final ArrayList<ActorListener> actorListeners = new ArrayList<ActorListener>();
/** Flag to tell if this node is a manager */
private boolean manager;
/** Actor of this node, if it is a manager */
protected vtkActor actor;
/** Geometry of this actor */
protected vtkPainterPolyDataMapper mapper;
protected vtkPolyData data;
/** Actor used for selection */
protected vtkActor selectionActor;
protected vtkPainterPolyDataMapper selectionMapper;
/** Last time this actor had been updated */
protected long lastUpdate;
/** Last time data of this actor had been modified */
protected long dataTime;
/** Last time this actor had been modified (data, color, visibility) */
protected long modificationTime;
/** Last time this node had been selected */
protected long selectionTime;
// Useful for debugging
private String debugName;
protected boolean visible = true;
protected boolean selected;
protected boolean pickable;
public final static double SHADING_ANGLE;
// default value from VTK sources (vtkPolyDataNormals.cxx)
public final static double DEFAULT_SHADING_ANGLE = 30;
static
{
String shadingAngleStr = System.getProperty(
"org.jcae.vtk.shading.angle", null);
if(shadingAngleStr == null)
SHADING_ANGLE = DEFAULT_SHADING_ANGLE;
else
SHADING_ANGLE = Double.parseDouble(shadingAngleStr);
}
public static interface ActorListener
{
void actorCreated(AbstractNode node, vtkActor actor);
void actorDeleted(AbstractNode node, vtkActor actor);
}
/**
* Customise actor when node is not selected.
*/
public interface ActorCustomiser
{
void customiseActor(vtkActor actor);
}
/**
* Customise mapper when node is not selected.
*/
public interface MapperCustomiser
{
void customiseMapper(vtkPainterPolyDataMapper mapper);
}
/**
* Default actor customiser, it does nothing.
*/
public static ActorCustomiser DEFAULT_ACTOR_CUSTOMISER =
new ActorCustomiser()
{
@Override
public void customiseActor(vtkActor actor) {}
};
/**
* Default mapper customiser, it calls vtkMapper.SetResolveCoincidentTopologyToPolygonOffset.
*/
public static MapperCustomiser DEFAULT_MAPPER_CUSTOMISER =
new MapperCustomiser()
{
@Override
public void customiseMapper(vtkPainterPolyDataMapper mapper)
{
Utils.setPolygonOffset(mapper, Utils.getOffsetFactor(),
Utils.getOffsetValue());
}
};
/**
* Default actor customiser when cells of this node are selected, it does nothing.
*/
public static ActorCustomiser DEFAULT_SELECTION_ACTOR_CUSTOMISER =
new ActorCustomiser()
{
@Override
public void customiseActor(vtkActor actor) {}
};
/**
* Default mapper customiser when cells of this node are selected, it does nothing.
*/
public static MapperCustomiser DEFAULT_SELECTION_MAPPER_CUSTOMISER =
new MapperCustomiser()
{
@Override
public void customiseMapper(vtkPainterPolyDataMapper mapper) {}
};
protected ActorCustomiser actorCustomiser;
protected MapperCustomiser mapperCustomiser;
protected ActorCustomiser selectionActorCustomiser;
protected MapperCustomiser selectionMapperCustomiser;
/**
* Constructor. It must not be called directly, only by subclasses.
*
* @param parent parent node
*/
protected AbstractNode(Node parent)
{
this.parent = parent;
if(parent != null)
{
pickable = parent.pickable;
visible = parent.visible;
}
}
protected vtkActor createActor()
{
return new vtkActor();
}
public Node getParent()
{
return parent;
}
public abstract List<LeafNode> getLeaves();
/**
* Set pickable the actor of the node. If the node is not a manager,
* it does nothing.
* @param pickable
*/
public void setPickable(boolean pickable)
{
this.pickable = pickable;
if(actor != null)
actor.SetPickable(Utils.booleanToInt(pickable));
}
public boolean isPickable()
{
return pickable;
}
public void addActorListener(ActorListener listener)
{
actorListeners.add(listener);
}
public void removeActorListener(ActorListener listener)
{
actorListeners.remove(listener);
}
public vtkActor getActor()
{
return actor;
}
protected void fireActorCreated(vtkActor actor)
{
for (ActorListener listener : actorListeners)
listener.actorCreated(this, actor);
}
protected void fireActorDeleted(vtkActor actor)
{
for (ActorListener listener : actorListeners)
listener.actorDeleted(this, actor);
}
public void setActorCustomiser(ActorCustomiser actorCustomiser)
{
this.actorCustomiser = actorCustomiser;
timeStampModified();
}
public void setMapperCustomiser(MapperCustomiser mapperCustomiser)
{
this.mapperCustomiser = mapperCustomiser;
timeStampModified();
}
public ActorCustomiser getSelectionActorCustomiser()
{
if(selectionActorCustomiser != null)
return selectionActorCustomiser;
else if(parent != null)
selectionActorCustomiser = parent.getSelectionActorCustomiser();
if(selectionActorCustomiser == null)
selectionActorCustomiser = DEFAULT_SELECTION_ACTOR_CUSTOMISER;
return selectionActorCustomiser;
}
public void setSelectionActorCustomiser(ActorCustomiser selectionActorCustomiser)
{
this.selectionActorCustomiser = selectionActorCustomiser;
timeStampSelected();
}
public MapperCustomiser getSelectionMapperCustomiser()
{
if(selectionMapperCustomiser != null)
return selectionMapperCustomiser;
if(parent != null)
selectionMapperCustomiser = parent.getSelectionMapperCustomiser();
if(selectionMapperCustomiser == null)
selectionMapperCustomiser = DEFAULT_SELECTION_MAPPER_CUSTOMISER;
return selectionMapperCustomiser;
}
public void setSelectionMapperCustomiser(MapperCustomiser selectionMapperCustomiser)
{
this.selectionMapperCustomiser = selectionMapperCustomiser;
timeStampSelected();
}
public ActorCustomiser getActorCustomiser()
{
if(actorCustomiser != null)
return actorCustomiser;
if(parent != null)
actorCustomiser = parent.getActorCustomiser();
if(actorCustomiser == null)
actorCustomiser = DEFAULT_ACTOR_CUSTOMISER;
return actorCustomiser;
}
public MapperCustomiser getMapperCustomiser()
{
if(mapperCustomiser != null)
return mapperCustomiser;
if(parent != null)
mapperCustomiser = parent.getMapperCustomiser();
if(mapperCustomiser == null)
mapperCustomiser = DEFAULT_MAPPER_CUSTOMISER;
return mapperCustomiser;
}
protected void timeStampData()
{
dataTime = System.nanoTime();
// When data are modified, actor must be updated
timeStampModified();
if (!manager && parent != null)
parent.timeStampData();
}
protected void timeStampModified()
{
modificationTime = System.nanoTime();
if (!manager && parent != null)
parent.timeStampModified();
}
protected void timeStampSelected()
{
selectionTime = System.nanoTime();
if (!manager && parent != null)
parent.timeStampSelected();
}
public boolean isVisible()
{
return visible;
}
public void setVisible(boolean visible)
{
if(this.visible == visible)
return;
this.visible = visible;
if(actor != null)
actor.SetVisibility(Utils.booleanToInt(visible));
timeStampModified();
// If node is not a manager, its manager have to update
// their data.
if (!manager && parent != null)
parent.timeStampData();
}
protected abstract void refresh();
protected void createData(LeafNode.DataProvider dataProvider)
{
data = new vtkPolyData();
data.SetPoints(Utils.createPoints(dataProvider.getNodes()));
vtkCellArray cells = Utils.createCells(dataProvider.getNbrOfVertices(),
dataProvider.getVertices());
data.SetVerts(cells);
cells=Utils.createCells(dataProvider.getNbrOfLines(), dataProvider.getLines());
data.SetLines(cells);
cells=Utils.createCells(dataProvider.getNbrOfPolys(), dataProvider.getPolys());
data.SetPolys(cells);
if(LOGGER.isLoggable(Level.FINEST))
{
LOGGER.finest("Number of points : " + data.GetPoints().GetNumberOfPoints());
LOGGER.finest("Number of vertices : " + data.GetVerts().GetNumberOfCells());
LOGGER.finest("Number of lines : " + data.GetLines().GetNumberOfCells());
LOGGER.finest("Number of polys : " + data.GetPolys().GetNumberOfCells());
LOGGER.finest("vertex coherance : " + Utils.isMeshCoherent(dataProvider.getNodes(), dataProvider.getVertices()));
LOGGER.finest("line coherance : " + Utils.isMeshCoherent(dataProvider.getNodes(), dataProvider.getLines()));
LOGGER.finest("polys coherance : " + Utils.isMeshCoherent(dataProvider.getNodes(), dataProvider.getPolys()));
}
if(dataProvider.getNormals() == null)
return;
// Compute normals that are not given
vtkPolyDataNormals algoNormals = new vtkPolyDataNormals();
algoNormals.SetInputDataObject(data);
algoNormals.SplittingOff();
algoNormals.FlipNormalsOff();
algoNormals.AutoOrientNormalsOff();
algoNormals.ConsistencyOff();
algoNormals.ComputePointNormalsOn();
algoNormals.SetFeatureAngle(getShadingAngle());
algoNormals.Update();
data = algoNormals.GetOutput();
float[] javaNormals = dataProvider.getNormals();
vtkPointData pointData = data.GetPointData();
vtkFloatArray computedNormals = (vtkFloatArray) pointData.GetNormals();
if(computedNormals != null)
{
float[] javaComputedNormals = computedNormals.GetJavaArray();
// If the normals are not computed change them by the normals computed by the meshes
for(int i = 0 ; i < javaComputedNormals.length ; i+= 3)
{
if(javaNormals[i] == 0. && javaNormals[i + 1] == 0. && javaNormals[i + 2] == 0.)
{
javaNormals[i] = javaComputedNormals[i];
javaNormals[i + 1] = javaComputedNormals[i + 1];
javaNormals[i + 2] = javaComputedNormals[i + 2];
}
}
}
vtkFloatArray normals = new vtkFloatArray();
normals.SetNumberOfComponents(3);
normals.SetJavaArray(javaNormals);
pointData.SetNormals(normals);
//fireDataModified(data);
}
protected void deleteData()
{
if(data != null)
{
data = null;
}
if(actor != null)
{
fireActorDeleted(actor);
Utils.delete(actor);
actor = null;
}
if(mapper != null)
{
mapper = null;
}
}
void deleteSelectionActor()
{
if(selectionActor == null)
return;
fireActorDeleted(selectionActor);
if(selectionActor != null)
{
selectionActor = null;
}
if(selectionMapper != null)
{
selectionMapper = null;
}
}
public void select()
{
if(selected)
return;
selected = true;
timeStampSelected();
}
public void unselect()
{
if(!selected)
return;
selected = false;
timeStampSelected();
}
public boolean isSelected()
{
return selected;
}
/**
* Declare some cells as being selected. This is called by
* {@link Scene#pick(vtk.vtkCanvas, int[], int[])} to store the list
* of selected cells in each node, and this result is used by renderer.
*
* @param cellSelection list of cell ids being selected
*/
abstract void setCellSelection(PickContext pickContext, int [] cellSelection);
abstract void clearCellSelection();
public void setManager(boolean manager)
{
if(this.manager == manager)
return;
this.manager = manager;
if(!this.manager)
deleteData();
timeStampModified();
}
public boolean isManager()
{
return manager;
}
public void setDebugName(String name)
{
debugName = name;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(getClass().getName()+"@"+Integer.toHexString(hashCode()));
if (debugName != null)
sb.append(" "+debugName);
if (manager)
sb.append(" manager");
if (selected)
sb.append(" selected");
if (actor != null)
{
sb.append(" actor@"+Integer.toHexString(actor.hashCode()));
if (actor.GetVisibility() != 0)
sb.append(" visible");
if (actor.GetPickable() != 0)
sb.append(" pickable");
}
if (selectionActor != null)
{
sb.append(" selectionActor@"+Integer.toHexString(selectionActor.hashCode()));
if (selectionActor.GetVisibility() != 0)
sb.append(" visible");
if (selectionActor.GetPickable() != 0)
sb.append(" pickable");
}
return sb.toString();
}
/**
* To be overriden by subclasse to change the shading angle.
* This implementation return the SHADING_ANGLE constant.
*/
protected double getShadingAngle()
{
return SHADING_ANGLE;
}
public void setEdgeVisible(boolean b)
{
if(actor != null)
{
vtkProperty p = actor.GetProperty();
p.SetEdgeVisibility(Utils.booleanToInt(b));
}
}
public void setCulling(boolean front, boolean back)
{
if(actor != null)
{
vtkProperty p = actor.GetProperty();
p.SetFrontfaceCulling(Utils.booleanToInt(front));
p.SetBackfaceCulling(Utils.booleanToInt(back));
}
}
}