package plugins.kernel.roi.roi3d;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import icy.canvas.IcyCanvas;
import icy.common.CollapsibleEvent;
import icy.math.Line3DIterator;
import icy.painter.Anchor3D;
import icy.resource.ResourceUtil;
import icy.roi.ROI;
import icy.roi.ROIEvent;
import icy.sequence.Sequence;
import icy.type.geom.Line3D;
import icy.type.geom.Polyline3D;
import icy.type.point.Point3D;
import icy.type.point.Point5D;
import icy.util.StringUtil;
import icy.util.XMLUtil;
import icy.vtk.IcyVtkPanel;
import plugins.kernel.canvas.VtkCanvas;
import vtk.vtkTubeFilter;
/**
* 3D Polyline ROI
*
* @author Stephane Dallongeville
*/
public class ROI3DPolyLine extends ROI3DShape
{
public class ROI3DPolyLinePainter extends ROI3DShapePainter
{
// extra VTK 3D objects
protected vtkTubeFilter tubeFilter;
public ROI3DPolyLinePainter()
{
super();
// don't create VTK object on constructor
tubeFilter = null;
}
@Override
protected void finalize() throws Throwable
{
super.finalize();
// release allocated VTK resources
if (tubeFilter != null)
tubeFilter.Delete();
};
@Override
protected void initVtkObjects()
{
super.initVtkObjects();
// init specific tube filter
tubeFilter = new vtkTubeFilter();
tubeFilter.SetInputData(polyData);
tubeFilter.SetRadius(1d);
tubeFilter.CappingOn();
tubeFilter.SetNumberOfSides(8);
// tubeFilter.SidesShareVerticesOff();
polyMapper.SetInputConnection(tubeFilter.GetOutputPort());
}
/**
* update 3D painter for 3D canvas (called only when VTK is loaded).
*/
@Override
protected void rebuildVtkObjects()
{
super.rebuildVtkObjects();
final VtkCanvas canvas = canvas3d.get();
// canvas was closed
if (canvas == null)
return;
final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
// canvas was closed
if (vtkPanel == null)
return;
// sub VTK object not yet initialized (it can happen, have to check why ??)
if (tubeFilter == null)
return;
// actor can be accessed in canvas3d for rendering so we need to synchronize access
vtkPanel.lock();
try
{
// just be sure the tube filter is also up to date
tubeFilter.Update();
}
finally
{
vtkPanel.unlock();
}
}
protected void updateVtkTubeRadius()
{
// VTK object not yet initialized
if (actor == null)
return;
final VtkCanvas canvas = canvas3d.get();
// canvas was closed
if (canvas == null)
return;
final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
// canvas was closed
if (vtkPanel == null)
return;
// sub VTK object not yet initialized (it can happen, have to check why ??)
if (tubeFilter == null)
return;
// update tube radius base on canvas scale X and image scale X
final double radius = canvas.canvasToImageLogDeltaX((int) getStroke()) * scaling[0];
if (tubeFilter.GetRadius() != radius)
{
// actor can be accessed in canvas3d for rendering so we need to synchronize access
vtkPanel.lock();
try
{
tubeFilter.SetRadius(radius);
tubeFilter.Update();
}
finally
{
vtkPanel.unlock();
}
// need to repaint
painterChanged();
}
}
@Override
public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
{
super.drawROI(g, sequence, canvas);
// update VTK tube radius if needed
if (canvas instanceof VtkCanvas)
updateVtkTubeRadius();
}
@Override
protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
{
drawShape(g, sequence, canvas, simplified, false);
}
}
/**
*
*/
public ROI3DPolyLine(Point3D pt)
{
super(new Polyline3D());
// add points to list
final Anchor3D anchor = createAnchor(pt);
// just add the new point at last position
addPoint(anchor);
// always select
anchor.setSelected(true);
updatePolyline();
// set icon
setIcon(ResourceUtil.ICON_ROI_POLYLINE);
}
/**
* Generic constructor for interactive mode
*/
public ROI3DPolyLine(Point5D pt)
{
this(pt.toPoint3D());
}
public ROI3DPolyLine(Polyline3D polyline)
{
this(new Point3D.Double());
setPolyline3D(polyline);
}
public ROI3DPolyLine(List<Point3D> points)
{
this(new Point3D.Double());
setPoints(points);
}
public ROI3DPolyLine()
{
this(new Point3D.Double());
}
@Override
public String getDefaultName()
{
return "PolyLine3D";
}
@Override
protected ROI3DPolyLinePainter createPainter()
{
return new ROI3DPolyLinePainter();
}
public Polyline3D getPolyline3D()
{
return (Polyline3D) shape;
}
public void setPoints(List<Point3D> pts)
{
beginUpdate();
try
{
removeAllPoint();
for (Point3D pt : pts)
addNewPoint(pt, false);
}
finally
{
endUpdate();
}
}
public void setPolyline3D(Polyline3D value)
{
beginUpdate();
try
{
removeAllPoint();
for (int i = 0; i < value.npoints; i++)
addNewPoint(new Point3D.Double(value.xpoints[i], value.ypoints[i], value.zpoints[i]), false);
}
finally
{
endUpdate();
}
}
@Override
protected double getTotalDistance(List<Point3D> points, double factorX, double factorY, double factorZ)
{
// for polyline the total length don't need last point connection
return Point3D.getTotalDistance(points, factorX, factorY, factorZ, false);
}
@Override
public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
{
if ((width <= 0) || (height <= 0))
return new boolean[0];
final List<Point3D> points = getPointsInternal();
final boolean[] result = new boolean[width * height];
// 2D bounds
final Rectangle bounds2d = new Rectangle(x, y, width, height);
for (int i = 1; i < points.size(); i++)
drawLine3DInBooleanMask2D(bounds2d, result, z, points.get(i - 1), points.get(i));
return result;
}
public static void drawLine3DInBooleanMask2D(Rectangle bounds2d, boolean[] result, int z, Point3D p1, Point3D p2)
{
final Line2D l = new Line2D.Double(p1.getX(), p1.getY(), p2.getX(), p2.getY());
// 2D intersection ?
if (l.intersects(bounds2d))
{
// 3D intersection ?
if (((p1.getZ() <= z) && (p2.getZ() >= z)) || ((p2.getZ() <= z) && (p1.getZ() >= z)))
{
final int bx = bounds2d.x;
final int by = bounds2d.y;
final int pitch = bounds2d.width;
final Line3DIterator it = new Line3DIterator(new Line3D(p1, p2), 1d);
while (it.hasNext())
{
final Point3D pt = it.next();
// same Z ?
if (Math.floor(pt.getZ()) == z)
{
final int x = (int) Math.floor(pt.getX());
final int y = (int) Math.floor(pt.getY());
// draw inside the mask
if (bounds2d.contains(x, y))
result[(x - bx) + ((y - by) * pitch)] = true;
}
}
}
}
}
/**
* roi changed
*/
@Override
public void onChanged(CollapsibleEvent object)
{
final ROIEvent event = (ROIEvent) object;
// do here global process on ROI change
switch (event.getType())
{
case ROI_CHANGED:
// refresh shape
updatePolyline();
break;
case FOCUS_CHANGED:
((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
break;
case SELECTION_CHANGED:
final boolean s = isSelected();
// update controls point state given the selection state of the ROI
synchronized (controlPoints)
{
for (Anchor3D pt : controlPoints)
{
pt.setVisible(s);
if (!s)
pt.setSelected(false);
}
}
((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
break;
case PROPERTY_CHANGED:
final String property = event.getPropertyName();
if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR)
|| StringUtil.equals(property, PROPERTY_OPACITY))
((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
break;
default:
break;
}
super.onChanged(object);
}
@Override
public double computeNumberOfPoints()
{
return 0d;
}
@Override
public boolean contains(ROI roi)
{
return false;
}
protected void updatePolyline()
{
final int len = controlPoints.size();
final double ptsX[] = new double[len];
final double ptsY[] = new double[len];
final double ptsZ[] = new double[len];
for (int i = 0; i < len; i++)
{
final Anchor3D pt = controlPoints.get(i);
ptsX[i] = pt.getX();
ptsY[i] = pt.getY();
ptsZ[i] = pt.getZ();
}
final Polyline3D polyline3d = getPolyline3D();
// we can have a problem here if we try to redraw while we are modifying the polygon points
synchronized (polyline3d)
{
polyline3d.npoints = len;
polyline3d.xpoints = ptsX;
polyline3d.ypoints = ptsY;
polyline3d.zpoints = ptsZ;
polyline3d.calculateLines();
}
// the shape should have been rebuilt here
((ROI3DPolyLinePainter) painter).needRebuild = true;
}
@Override
public boolean loadFromXML(Node node)
{
beginUpdate();
try
{
if (!super.loadFromXML(node))
return false;
removeAllPoint();
final ArrayList<Node> nodesPoint = XMLUtil.getChildren(XMLUtil.getElement(node, ID_POINTS), ID_POINT);
if (nodesPoint != null)
{
for (Node n : nodesPoint)
{
final Anchor3D pt = createAnchor(new Point3D.Double());
pt.loadPositionFromXML(n);
addPoint(pt);
}
}
}
finally
{
endUpdate();
}
return true;
}
@Override
public boolean saveToXML(Node node)
{
if (!super.saveToXML(node))
return false;
final Element dependances = XMLUtil.setElement(node, ID_POINTS);
for (Anchor3D pt : controlPoints)
pt.savePositionToXML(XMLUtil.addElement(dependances, ID_POINT));
return true;
}
}