/*
* 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 gnu.trove.list.array.TIntArrayList;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import vtk.vtkActor;
import vtk.vtkAlgorithmOutput;
import vtk.vtkCellData;
import vtk.vtkExtractSelectedPolyDataIds;
import vtk.vtkIdTypeArray;
import vtk.vtkIntArray;
import vtk.vtkLookupTable;
import vtk.vtkPainterPolyDataMapper;
import vtk.vtkPolyData;
import vtk.vtkPolyDataMapper;
import vtk.vtkSelection;
import vtk.vtkSelectionNode;
/**
*
* @author Julian Ibarz
*/
public class Node extends AbstractNode
{
private static final Logger LOGGER = Logger.getLogger(Node.class.getName());
private final ArrayList<AbstractNode> children = new ArrayList<AbstractNode>();
// Datas if the node manage
private TIntArrayList offsetsVertices;
private TIntArrayList offsetsLines;
private TIntArrayList offsetsPolys;
private int nbrOfVertices;
private int nbrOfLines;
private int nbrOfPolys;
// Lookup table for color of leaves
private vtkLookupTable table;
private final ArrayList<ChildCreationListener> childCreationListeners = new ArrayList<ChildCreationListener>();
private static class NodeData extends LeafNode.DataProvider
{
NodeData(float[] nodes, float[] normals, int nbrOfVertices, int[] vertices, int nbrOfLines, int[] lines, int nbrOfPolys, int[] polys)
{
this.nodes = nodes;
this.normals = normals;
this.nbrOfVertices = nbrOfVertices;
this.vertices = vertices;
this.nbrOfLines = nbrOfLines;
this.lines = lines;
this.nbrOfPolys = nbrOfPolys;
this.polys = polys;
}
@Override
public float[] getNormals()
{
return normals;
}
@Override
public int[] getLines()
{
return lines;
}
@Override
public int[] getPolys()
{
return polys;
}
@Override
public int[] getVertices()
{
return vertices;
}
@Override
public float[] getNodes()
{
return nodes;
}
}
public Node(Node parent)
{
super(parent);
if(parent != null)
{
parent.addChild(this);
// Inherits child creation listeners
childCreationListeners.addAll(parent.childCreationListeners);
}
}
LeafNode getLeafNodeFromCell(int cellID)
{
if (!isManager())
throw new RuntimeException("Node is not a manager");
if (cellID < 0 || cellID >= data.GetNumberOfCells())
throw new RuntimeException("cellID out of bounds");
List<LeafNode> leaves = getLeaves();
int ID = ((vtkIntArray) data.GetCellData().GetScalars()).GetValue(cellID);
return leaves.get(ID);
}
/**
* Set pickable the actor of the node. If the node is not a manager,
* it processes its children recursively.
* @param pickable
*/
@Override
public void setPickable(boolean pickable)
{
if (isManager())
{
super.setPickable(pickable);
return;
}
for(AbstractNode child : children)
child.setPickable(pickable);
}
@Override
public List<LeafNode> getLeaves()
{
// Do not keep the leaves, just compute
ArrayList<LeafNode> toReturn = new ArrayList<LeafNode>();
for (AbstractNode child : children)
toReturn.addAll(child.getLeaves());
return toReturn;
}
public interface ChildCreationListener
{
void childCreated(AbstractNode node);
void childDeleted(AbstractNode node);
}
public void addChild(AbstractNode child)
{
if(children.add(child))
{
for(ChildCreationListener listener : childCreationListeners)
listener.childCreated(child);
timeStampData();
}
}
public void removeChild(AbstractNode child)
{
if (children.remove(child))
{
child.deleteData();
child.deleteSelectionActor();
for(ChildCreationListener listener : childCreationListeners)
listener.childDeleted(child);
timeStampData();
}
}
public void removeAllChildren()
{
for (AbstractNode child : new ArrayList<AbstractNode>(children))
removeChild(child);
}
public void addChildCreationListener(ChildCreationListener listener)
{
childCreationListeners.add(listener);
}
public void removeChildCreationListener(ChildCreationListener listener)
{
childCreationListeners.remove(listener);
}
@Override
public void setVisible(boolean visible)
{
for (AbstractNode child : children)
child.setVisible(visible);
super.setVisible(visible);
}
@Override
public void refresh()
{
for (AbstractNode child : children)
child.refresh();
if (!isManager())
{
lastUpdate = System.nanoTime();
return;
}
List<LeafNode> leaves = null;
if(lastUpdate <= dataTime || lastUpdate <= selectionTime)
{
//Loaded data provider are needed in refreshData and
//refreshHighlight
leaves = getLeaves();
for (LeafNode leaf : leaves)
leaf.getDataProvider().load();
}
// Were data modified?
if (lastUpdate <= dataTime)
refreshData(leaves);
// Was actor modified?
if (lastUpdate <= modificationTime)
refreshActor();
// Did selection happen?
if (lastUpdate <= selectionTime)
refreshHighlight();
if(leaves != null)
for (LeafNode leaf : leaves)
leaf.getDataProvider().unLoad();
lastUpdate = System.nanoTime();
}
private void refreshData(List<LeafNode> leaves)
{
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.finest("Refresh data for "+this);
// Compute the sizes
int nodesSize = 0;
int verticesSize = 0;
int linesSize = 0;
int polysSize = 0;
nbrOfVertices = 0;
nbrOfLines = 0;
nbrOfPolys = 0;
boolean buildNormals = true;
int numberOfLeaves = leaves.size();
offsetsVertices = new TIntArrayList(numberOfLeaves + 1);
offsetsLines = new TIntArrayList(numberOfLeaves + 1);
offsetsPolys = new TIntArrayList(numberOfLeaves + 1);
for (LeafNode leaf : leaves)
{
offsetsVertices.add(nbrOfVertices);
offsetsLines.add(nbrOfLines);
offsetsPolys.add(nbrOfPolys);
if (!leaf.isVisible())
continue;
LeafNode.DataProvider dataProvider = leaf.getDataProvider();
nodesSize += dataProvider.getNodes().length;
verticesSize += dataProvider.getVertices().length;
linesSize += dataProvider.getLines().length;
polysSize += dataProvider.getPolys().length;
nbrOfVertices += dataProvider.getNbrOfVertices();
nbrOfLines += dataProvider.getNbrOfLines();
nbrOfPolys += dataProvider.getNbrOfPolys();
if (dataProvider.getNormals() == null)
buildNormals = false;
}
offsetsVertices.add(nbrOfVertices);
offsetsLines.add(nbrOfLines);
offsetsPolys.add(nbrOfPolys);
// If there is no nodes then there is no normals
if (nodesSize == 0)
buildNormals = false;
// Compute the arrays
float[] nodes = new float[nodesSize];
float[] normals = null;
if (buildNormals)
normals = new float[nodesSize];
int[] vertices = new int[verticesSize];
int[] lines = new int[linesSize];
int[] polys = new int[polysSize];
int offsetNode = 0;
int offsetVertex = 0;
int offsetLine = 0;
int offsetPoly = 0;
for (int i = 0; i < numberOfLeaves; ++i)
{
LeafNode leaf = leaves.get(i);
if (!leaf.isVisible())
continue;
LeafNode.DataProvider dataProvider = leaf.getDataProvider();
final int numberOfNode = offsetNode / 3;
float[] nodesNode = dataProvider.getNodes();
System.arraycopy(nodesNode, 0, nodes, offsetNode, nodesNode.length);
if (buildNormals)
{
float[] normalsNode = dataProvider.getNormals();
if (normalsNode == null)
Arrays.fill(normals, offsetNode, offsetNode + nodesNode.length, 0.f);
else
System.arraycopy(normalsNode, 0, normals, offsetNode, normalsNode.length);
}
offsetNode += nodesNode.length;
int[] verticesNode = dataProvider.getVertices();
System.arraycopy(verticesNode, 0, vertices, offsetVertex, verticesNode.length);
// Make an offset
for (int j = offsetVertex; j < offsetVertex + verticesNode.length;)
{
vertices[++j] += numberOfNode;
++j;
}
offsetVertex += verticesNode.length;
int[] linesNode = dataProvider.getLines();
System.arraycopy(linesNode, 0, lines, offsetLine, linesNode.length);
// Make an offset
for (int j = offsetLine; j < offsetLine + linesNode.length;)
{
lines[++j] += numberOfNode;
lines[++j] += numberOfNode;
++j;
}
offsetLine += linesNode.length;
int[] polysNode = dataProvider.getPolys();
System.arraycopy(polysNode, 0, polys, offsetPoly, polysNode.length);
// Make an offset
for (int j = offsetPoly; j < offsetPoly + polysNode.length;)
{
int size = polys[j++];
for (int c = 0; c < size; ++c)
polys[j++] += numberOfNode;
}
offsetPoly += polysNode.length;
}
// Compute the id association array
int[] ids = new int[nbrOfVertices + nbrOfLines + nbrOfPolys];
for (int leafIndex = 0; leafIndex < numberOfLeaves; ++leafIndex)
{
// Vertex part
int begin = offsetsVertices.get(leafIndex);
int end = offsetsVertices.get(leafIndex + 1);
Arrays.fill(ids, begin, end, leafIndex);
// Line part
begin = nbrOfVertices + offsetsLines.get(leafIndex);
end = nbrOfVertices + offsetsLines.get(leafIndex + 1);
Arrays.fill(ids, begin, end, leafIndex);
// Poly part
begin = nbrOfVertices + nbrOfLines + offsetsPolys.get(leafIndex);
end = nbrOfVertices + nbrOfLines + offsetsPolys.get(leafIndex + 1);
Arrays.fill(ids, begin, end, leafIndex);
}
NodeData nodeData = new NodeData(nodes, normals, nbrOfVertices, vertices, nbrOfLines, lines, nbrOfPolys, polys);
createData(nodeData);
vtkIntArray idsNative = new vtkIntArray();
idsNative.SetJavaArray(ids);
vtkCellData cellData = data.GetCellData();
cellData.SetScalars(idsNative);
cellData = null;
idsNative = null;
timeStampData();
if(mapper == null)
{
mapper = new vtkPainterPolyDataMapper();
//This should help reducing the memory footprint of display list
//but it don't. Must be kept for further investigations.
/*vtkInformation i = mapper.GetInformation();
i.Set(Utils.CONSERVE_MEMORY, 1);
i.Set(Utils.HIGH_QUALITY, 0);
vtkDefaultPainter vdp = (vtkDefaultPainter) mapper.GetPainter();
System.out.println(vdp.GetDisplayListPainter().GetInformation());*/
}
getMapperCustomiser().customiseMapper(mapper);
mapper.SetInputData(data);
mapper.Update();
}
// Must always be called after refreshData
private void refreshActor()
{
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.finest("Refresh actor for "+this);
boolean actorCreated = (actor == null);
if(actorCreated)
actor = createActor();
getActorCustomiser().customiseActor(actor);
actor.SetMapper(mapper);
actor.SetVisibility(Utils.booleanToInt(visible));
actor.SetPickable(Utils.booleanToInt(pickable));
// Update mapper, colors may have changed
List<LeafNode> leaves = getLeaves();
int numberOfLeaves = leaves.size();
table = new vtkLookupTable();
table.SetNumberOfTableValues(numberOfLeaves);
table.SetTableRange(0, numberOfLeaves);
for (int i = 0; i < numberOfLeaves; ++i)
{
LeafNode leaf = leaves.get(i);
Color color = leaf.getColor();
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.finest("Compound: set color to "+color+" (opacity="+color.getAlpha()+")");
table.SetTableValue(i, (double) color.getRed() / 255., (double) color.getGreen() / 255., (double) color.getBlue() / 255., (double) color.getAlpha() / 255.);
}
mapper.SetLookupTable(table);
mapper.UseLookupTableScalarRangeOn();
mapper.SetScalarModeToUseCellData();
if (actorCreated)
{
fireActorCreated(actor);
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, "New actor created: vtkActor@"+Integer.toHexString(actor.hashCode()));
}
}
@Override
protected void deleteData()
{
super.deleteData();
offsetsVertices = null;
offsetsLines = null;
offsetsPolys = null;
table = null;
for(AbstractNode n : children)
n.deleteData();
}
private void refreshHighlight()
{
if (LOGGER.isLoggable(Level.FINEST))
LOGGER.log(Level.FINEST, "Refresh highlight for "+this);
if (selected)
{
// The whole actor is selected, so display it
// as highlighted.
mapper.ScalarVisibilityOff();
getSelectionActorCustomiser().customiseActor(actor);
getSelectionMapperCustomiser().customiseMapper(mapper);
deleteSelectionActor();
}
else
{
// Reset original actor colors
mapper.ScalarVisibilityOn();
getActorCustomiser().customiseActor(actor);
getMapperCustomiser().customiseMapper(mapper);
refreshSelectionActor();
}
}
private void refreshSelectionActor()
{
TIntArrayList selection = new TIntArrayList(nbrOfVertices + nbrOfLines + nbrOfPolys);
int leafIndex = -1;
for (LeafNode leaf : getLeaves())
{
leafIndex++;
if (leaf.selected)
{
// If a node is selected, select all cells
// Vertices
int vBegin = offsetsVertices.get(leafIndex);
int vEnd = offsetsVertices.get(leafIndex + 1);
// Lines
int lBegin = offsetsLines.get(leafIndex) + nbrOfVertices;
int lEnd = offsetsLines.get(leafIndex + 1) + nbrOfVertices;
// Polys
int pBegin = offsetsPolys.get(leafIndex) + nbrOfVertices + nbrOfLines;
int pEnd = offsetsPolys.get(leafIndex + 1) + nbrOfVertices + nbrOfLines;
selection.ensureCapacity(selection.size() +
(vEnd + 1 - vBegin) +
(lEnd + 1 - lBegin) +
(pEnd + 1 - pBegin));
// Add vertices
for (int j = vBegin; j < vEnd; ++j)
selection.add(j);
// Add lines
for (int j = lBegin; j < lEnd; ++j)
selection.add(j);
// Add polys
for (int j = pBegin; j < pEnd; ++j)
selection.add(j);
}
else if (leaf.hasCellSelection())
{
int[] cellSelection = leaf.getCellSelection();
selection.ensureCapacity(selection.size()+cellSelection.length);
for (int j = 0; j < cellSelection.length; ++j)
selection.add(leafIndexToNodeIndex(leaf, leafIndex, cellSelection[j]));
}
}
if (selection.isEmpty())
{
deleteSelectionActor();
return;
}
boolean actorCreated = (selectionActor == null);
if (actorCreated)
{
selectionActor = new vtkActor();
selectionActor.PickableOff();
}
getSelectionActorCustomiser().customiseActor(selectionActor);
if(selectionMapper == null)
selectionMapper = new vtkPainterPolyDataMapper();
selectionMapper.ScalarVisibilityOff();
selectionMapper.SetInputConnection(selectInto(data, selection.toArray()));
selectionActor.SetMapper(selectionMapper);
getSelectionMapperCustomiser().customiseMapper(selectionMapper);
if (actorCreated)
fireActorCreated(selectionActor);
}
private final int nodeIndexToLeafIndex(int leaf, int index)
{
if (0 <= index && index < nbrOfVertices)
return index - offsetsVertices.getQuick(leaf);
index -= nbrOfVertices;
if (0 <= index && index < nbrOfLines)
return index - offsetsLines.getQuick(leaf);
index -= nbrOfLines;
if (0 <= index && index < nbrOfPolys)
return index - offsetsPolys.getQuick(leaf);
throw new IllegalArgumentException("Wrong index: "+index);
}
private final int leafIndexToNodeIndex(LeafNode leaf, int leafIndex, int index)
{
LeafNode.DataProvider leafDataProvider = leaf.getDataProvider();
int numberOfVerticesLeaf = leafDataProvider.getNbrOfVertices();
int numberOfLinesLeaf = leafDataProvider.getNbrOfLines();
int numberOfPolysLeaf = leafDataProvider.getNbrOfPolys();
if (0 <= index && index < numberOfVerticesLeaf)
return index + offsetsVertices.getQuick(leafIndex);
index -= numberOfVerticesLeaf;
if (0 <= index && index < numberOfLinesLeaf)
return index + nbrOfVertices + offsetsLines.getQuick(leafIndex);
index -= numberOfLinesLeaf;
if (0 <= index && index < numberOfPolysLeaf)
return index + nbrOfVertices + nbrOfLines + offsetsPolys.getQuick(leafIndex);
throw new IllegalArgumentException("Wrong index: "+index);
}
private vtkAlgorithmOutput selectInto(vtkPolyData input, int[] cellID)
{
vtkSelection selection = new vtkSelection();
vtkSelectionNode selectionNode = new vtkSelectionNode();
//sel.ReleaseDataFlagOn();
// 4 MEANS INDICES (see the enumeration)
selectionNode.GetProperties().Set(selectionNode.CONTENT_TYPE(), 4);
// 0 MEANS CELLS
selectionNode.GetProperties().Set(selectionNode.FIELD_TYPE(), 0);
// list of cells to be selected
vtkIdTypeArray arr = Utils.setValues(cellID);
selectionNode.SetSelectionList(arr);
selection.AddNode(selectionNode);
vtkExtractSelectedPolyDataIds selFilter = new vtkExtractSelectedPolyDataIds();
selFilter.ReleaseDataFlagOn();
selFilter.SetInputData(1, selection);
selFilter.SetInputData(0, input);
return selFilter.GetOutputPort();
}
@Override
void setCellSelection(PickContext pickContext, int [] cellSelection)
{
if (!isManager())
throw new RuntimeException("The Node has to be a manager to manage the selection");
int[] ids = ((vtkIntArray) data.GetCellData().GetScalars()).GetJavaArray();
List<LeafNode> leaves = getLeaves();
for (LeafNode leaf : leaves)
leaf.clearCellSelection();
TIntArrayList [] selectionChildren = new TIntArrayList[leaves.size()];
for (int i = 0; i < leaves.size(); ++i)
selectionChildren[i] = new TIntArrayList();
// Compute the selections
for (int cellID : cellSelection)
{
if(cellID >= ids.length)
{
LOGGER.log(Level.SEVERE,
"cellID {0} out of bounds {1}.\ncellSelection={2}\nids={3}",
new Object[]{cellID, ids.length,
Arrays.toString(cellSelection), Arrays.toString(ids)});
}
else
{
int nodeID = ids[cellID];
selectionChildren[nodeID].add(nodeIndexToLeafIndex(nodeID, cellID));
}
}
// Send the selections to the children
for (int i = 0; i < leaves.size(); ++i)
{
if (!selectionChildren[i].isEmpty())
leaves.get(i).setCellSelection(pickContext, selectionChildren[i].toArray());
}
timeStampSelected();
}
@Override
public void clearCellSelection()
{
for (LeafNode leaf : getLeaves())
leaf.clearCellSelection();
timeStampSelected();
}
public List<AbstractNode> getChildren()
{
return Collections.unmodifiableList(children);
}
@Override
public void setEdgeVisible(boolean b)
{
super.setEdgeVisible(b);
for(AbstractNode f:getChildren())
f.setEdgeVisible(b);
}
@Override
public void setCulling(boolean front, boolean back) {
super.setCulling(front, back);
for(AbstractNode f:getChildren())
f.setCulling(front, back);
}
}