/*
* 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.set.hash.TIntHashSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import vtk.vtkActor;
import vtk.vtkPlaneCollection;
import vtk.vtkProperty;
/**
* @author Julian Ibarz
*/
public abstract class Viewable extends MultiCanvas
{
private final static Logger LOGGER = Logger.getLogger(Viewable.class.getName());
/** The position of the mouse when the press event occurs */
private String name;
private final ArrayList<SelectionListener> selectionListeners =
new ArrayList<SelectionListener>();
protected final double tolerance = 0.002; // 0.2% of tolerance in function of the (far-near) distance
protected final Scene scene;
/** The rootNode node of the viewable */
protected final Node rootNode;
/** Set of selected nodes */
protected Set<LeafNode> selectionNode = new HashSet<LeafNode>();
/** Map of selected cells */
protected final Map<LeafNode, TIntHashSet> selectionCell = new HashMap<LeafNode, TIntHashSet>();
/** Flag to know if selection has changed */
protected boolean selectionChanged;
/** Flag to set selection in append or replace mode */
protected boolean appendSelection;
private SelectionType selectionType = SelectionType.NODE;
private int pixelTolerance;
public enum SelectionType
{
NODE,
CELL,
POINT
}
public Viewable()
{
scene = new Scene();
rootNode = new Node(null);
addNode(rootNode);
rootNode.addChildCreationListener(this);
rootNode.setSelectionActorCustomiser(new SelectionActorCustomiser());
}
protected class SelectionActorCustomiser implements AbstractNode.ActorCustomiser
{
@Override
public void customiseActor(vtkActor actor)
{
vtkProperty p = actor.GetProperty();
Utils.vtkPropertySetColor(p, selectionColor);
}
}
public void setSelectionType(SelectionType selectionType)
{
this.selectionType = selectionType;
}
public SelectionType getSelectionType()
{
return this.selectionType;
}
public int getPixelTolerance()
{
return pixelTolerance;
}
public void setPixelTolerance(int pixelTolerance)
{
this.pixelTolerance = pixelTolerance;
}
/**
* Return current mode of selection.
* If true, selection is added to current selection.
* If false, it replaces current selection.
* @return current mode of selection
*/
public boolean getAppendSelection()
{
return this.appendSelection;
}
/**
* Set mode of selection.
* If true, selection is added to current selection.
* If false, it replaces current selection.
* @param appendSelection
*/
public void setAppendSelection(boolean appendSelection)
{
this.appendSelection = appendSelection;
}
/**
* Pick and update informations about picked objects.
* {@link Scene#select(org.jcae.vtk.PickContext)} is called
* to perform picking and find selected nodes and cells,
* then {@link #manageSelection(org.jcae.vtk.PickContext)}
* updates selection state.
*
* @param pickContext object carrying informations about picking context
*/
public void performSelection(PickContext pickContext)
{
scene.select(pickContext);
manageSelection(pickContext);
}
/**
* Update selection state.
* <ol>
* <li>When {@link #selectionType} is NODE, {@link #selectionNode}
* set is updated to contain the new set of selected nodes,
* based on append mode, the set of previously selected nodes
* and the set of picked nodes. The {@link #selectionChanged}
* member is set to <code>true</code> if selection has changed.
* </li>
* <li>When {@link #selectionType} is CELL, {@link #selectionCell}
* map is updated to contain the new map of selected nodes,
* based on append mode, the map of previously selected nodes
* and the set of picked cells. Selected cells in leaves are
* also updated by calling
* {@link LeafNode#setCellSelection(org.jcae.vtk.PickContext, int[])}.
* The {@link #selectionChanged} member is set to <code>true</code>
* if anything has been picked.
* </li>
* </ol>
*
* @param pickContext object carrying informations about picking context
*/
protected void manageSelection(PickContext pickContext)
{
switch (selectionType)
{
case NODE:
manageNodeSelection(pickContext);
break;
case CELL:
manageCellSelection(pickContext);
break;
case POINT:
// No-op for now
break;
default:
throw new IllegalStateException();
}
if (selectionChanged)
fireSelectionChanged();
selectionChanged = false;
}
/**
* Update selectionCell to take previous selection into account.
*
* @param pickContext object carrying informations about picking context
*/
private void manageCellSelection(PickContext pickContext)
{
selectionNode.clear();
if (!appendSelection)
selectionCell.clear();
Set<LeafNode> oldSelection = new HashSet<LeafNode>(selectionCell.keySet());
Set<LeafNode> newSelection = pickContext.getSelectedNodes();
for (LeafNode node : newSelection)
{
selectionChanged = true;
TIntHashSet nodeCellSelection = selectionCell.get(node);
if (nodeCellSelection == null)
{
pickContext.addToSelectedNodes(node);
selectionCell.put(node,
new TIntHashSet(node.getCellSelection()));
}
else
{
for (int cell : node.getCellSelection())
{
// If already selected, we are in append mode, then delete it
if (!nodeCellSelection.add(cell))
nodeCellSelection.remove(cell);
}
// Send the new selection to the leaf
node.setCellSelection(pickContext, nodeCellSelection.toArray());
}
}
// Send old selection to leaves, it may have been reset by scene.select()
for (LeafNode leaf : oldSelection)
{
if (!newSelection.contains(leaf))
leaf.setCellSelection(pickContext, selectionCell.get(leaf).toArray());
}
if (selectionChanged)
LOGGER.finest("Selection changed");
else
LOGGER.finest("Selection not changed");
}
/**
* Update selectionNode to take previous selection into account.
*
* @param pickContext object carrying informations about picking context
*/
private void manageNodeSelection(PickContext pickContext)
{
selectionCell.clear();
HashSet<LeafNode> newSelection;
if (appendSelection)
newSelection = new HashSet<LeafNode>(selectionNode);
else
newSelection = new HashSet<LeafNode>();
for (LeafNode node : pickContext.getSelectedNodes())
{
// If already selected, we are in append mode, then delete it
if (newSelection.add(node))
node.select();
else
{
newSelection.remove(node);
node.unselect();
}
// Clear cell selection
node.clearCellSelection();
}
if (!selectionNode.equals(newSelection))
selectionChanged = true;
selectionNode = newSelection;
}
public void addSelectionListener(SelectionListener listener)
{
selectionListeners.add(listener);
}
public void removeSelectionListener(SelectionListener listener)
{
selectionListeners.remove(listener);
}
protected void fireSelectionChanged()
{
for (SelectionListener listener : selectionListeners)
listener.selectionChanged(this);
}
/**
* If you want the highlight disappears call highlight() afterwards
*/
protected void unselectAll()
{
for (LeafNode leaf : selectionCell.keySet())
leaf.clearCellSelection();
selectionCell.clear();
for (LeafNode leaf : selectionNode)
leaf.unselect();
selectionNode.clear();
}
@Override
void addNode(AbstractNode node)
{
super.addNode(node);
scene.addNode(node);
}
@Override
void removeNode(AbstractNode node)
{
scene.removeNode(node);
if(selectionNode.remove(node))
selectionChanged = true;
super.removeNode(node);
}
public void highlight()
{
// Refresh viewable
rootNode.refresh();
render();
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
@Override
public String toString()
{
if (name == null)
return super.toString();
else
return name;
}
public void removeClippingPlanes()
{
// Send empty plane collection to removeCanvas the older planes
vtkPlaneCollection empty = new vtkPlaneCollection();
setClippingPlanes(empty);
render();
}
void setClippingPlanes(vtkPlaneCollection planes)
{
scene.setClippingPlanes(planes);
}
void setPickable(boolean pickable)
{
scene.setPickable(pickable);
}
/** VTK garbage collect all nodes associated to this viewable */
public void delete()
{
lockCanvas();
rootNode.removeAllChildren();
rootNode.deleteData();
unlockCanvas();
}
}