001package plugins.kernel.roi.roi3d;
002
003import icy.canvas.IcyCanvas;
004import icy.common.CollapsibleEvent;
005import icy.math.Line3DIterator;
006import icy.painter.Anchor3D;
007import icy.resource.ResourceUtil;
008import icy.roi.ROI;
009import icy.roi.ROIEvent;
010import icy.sequence.Sequence;
011import icy.type.geom.Line3D;
012import icy.type.geom.Polyline3D;
013import icy.type.point.Point3D;
014import icy.type.point.Point5D;
015import icy.util.StringUtil;
016import icy.util.XMLUtil;
017import icy.vtk.IcyVtkPanel;
018
019import java.awt.Graphics2D;
020import java.awt.Rectangle;
021import java.awt.geom.Line2D;
022import java.util.ArrayList;
023import java.util.List;
024
025import org.w3c.dom.Element;
026import org.w3c.dom.Node;
027
028import plugins.kernel.canvas.VtkCanvas;
029import vtk.vtkTubeFilter;
030
031/**
032 * 3D Polyline ROI
033 * 
034 * @author Stephane Dallongeville
035 */
036public class ROI3DPolyLine extends ROI3DShape
037{
038    public class ROI3DPolyLinePainter extends ROI3DShapePainter
039    {
040        // extra VTK 3D objects
041        protected vtkTubeFilter tubeFilter;
042
043        public ROI3DPolyLinePainter()
044        {
045            super();
046
047            // don't create VTK object on constructor
048            tubeFilter = null;
049        }
050
051        @Override
052        protected void finalize() throws Throwable
053        {
054            super.finalize();
055
056            // release allocated VTK resources
057            if (tubeFilter != null)
058                tubeFilter.Delete();
059        };
060
061        @Override
062        protected void initVtkObjects()
063        {
064            super.initVtkObjects();
065
066            // init specific tube filter
067            tubeFilter = new vtkTubeFilter();
068            tubeFilter.SetInputData(polyData);
069            tubeFilter.SetRadius(1d);
070            tubeFilter.CappingOn();
071            tubeFilter.SetNumberOfSides(8);
072            // tubeFilter.SidesShareVerticesOff();
073            polyMapper.SetInputConnection(tubeFilter.GetOutputPort());
074        }
075
076        /**
077         * update 3D painter for 3D canvas (called only when VTK is loaded).
078         */
079        @Override
080        protected void rebuildVtkObjects()
081        {
082            super.rebuildVtkObjects();
083
084            final VtkCanvas canvas = canvas3d.get();
085            // canvas was closed
086            if (canvas == null)
087                return;
088
089            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
090            // canvas was closed
091            if (vtkPanel == null)
092                return;
093
094            // sub VTK object not yet initialized (it can happen, have to check why ??)
095            if (tubeFilter == null)
096                return;
097
098            // actor can be accessed in canvas3d for rendering so we need to synchronize access
099            vtkPanel.lock();
100            try
101            {
102                // just be sure the tube filter is also up to date
103                tubeFilter.Update();
104            }
105            finally
106            {
107                vtkPanel.unlock();
108            }
109        }
110
111        protected void updateVtkTubeRadius()
112        {
113            // VTK object not yet initialized
114            if (actor == null)
115                return;
116
117            final VtkCanvas canvas = canvas3d.get();
118            // canvas was closed
119            if (canvas == null)
120                return;
121
122            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
123            // canvas was closed
124            if (vtkPanel == null)
125                return;
126
127            // sub VTK object not yet initialized (it can happen, have to check why ??)
128            if (tubeFilter == null)
129                return;
130
131            // update tube radius base on canvas scale X and image scale X
132            final double radius = canvas.canvasToImageLogDeltaX((int) getStroke()) * scaling[0];
133
134            if (tubeFilter.GetRadius() != radius)
135            {
136                // actor can be accessed in canvas3d for rendering so we need to synchronize access
137                vtkPanel.lock();
138                try
139                {
140                    tubeFilter.SetRadius(radius);
141                    tubeFilter.Update();
142                }
143                finally
144                {
145                    vtkPanel.unlock();
146                }
147
148                // need to repaint
149                painterChanged();
150            }
151        }
152
153        @Override
154        public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
155        {
156            super.drawROI(g, sequence, canvas);
157
158            // update VTK tube radius if needed
159            if (canvas instanceof VtkCanvas)
160                updateVtkTubeRadius();
161        }
162
163        @Override
164        protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
165        {
166            drawShape(g, sequence, canvas, simplified, false);
167        }
168    }
169
170    /**
171     * 
172     */
173    public ROI3DPolyLine(Point3D pt)
174    {
175        super(new Polyline3D());
176
177        // add points to list
178        final Anchor3D anchor = createAnchor(pt);
179        // just add the new point at last position
180        addPoint(anchor);
181        // always select
182        anchor.setSelected(true);
183
184        updatePolyline();
185
186        // set icon
187        setIcon(ResourceUtil.ICON_ROI_POLYLINE);
188    }
189
190    /**
191     * Generic constructor for interactive mode
192     */
193    public ROI3DPolyLine(Point5D pt)
194    {
195        this(pt.toPoint3D());
196    }
197
198    public ROI3DPolyLine(Polyline3D polyline)
199    {
200        this(new Point3D.Double());
201
202        setPolyline3D(polyline);
203    }
204
205    public ROI3DPolyLine(List<Point3D> points)
206    {
207        this(new Point3D.Double());
208
209        setPoints(points);
210    }
211
212    public ROI3DPolyLine()
213    {
214        this(new Point3D.Double());
215    }
216
217    @Override
218    public String getDefaultName()
219    {
220        return "PolyLine3D";
221    }
222
223    @Override
224    protected ROI3DPolyLinePainter createPainter()
225    {
226        return new ROI3DPolyLinePainter();
227    }
228
229    public Polyline3D getPolyline3D()
230    {
231        return (Polyline3D) shape;
232    }
233
234    public void setPoints(List<Point3D> pts)
235    {
236        beginUpdate();
237        try
238        {
239            removeAllPoint();
240            for (Point3D pt : pts)
241                addNewPoint(pt, false);
242        }
243        finally
244        {
245            endUpdate();
246        }
247    }
248
249    public void setPolyline3D(Polyline3D value)
250    {
251        beginUpdate();
252        try
253        {
254            removeAllPoint();
255            for (int i = 0; i < value.npoints; i++)
256                addNewPoint(new Point3D.Double(value.xpoints[i], value.ypoints[i], value.zpoints[i]), false);
257        }
258        finally
259        {
260            endUpdate();
261        }
262    }
263
264    @Override
265    protected double getTotalDistance(List<Point3D> points, double factorX, double factorY, double factorZ)
266    {
267        // for polyline the total length don't need last point connection
268        return Point3D.getTotalDistance(points, factorX, factorY, factorZ, false);
269    }
270
271    @Override
272    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
273    {
274        if ((width <= 0) || (height <= 0))
275            return new boolean[0];
276
277        final List<Point3D> points = getPointsInternal();
278        final boolean[] result = new boolean[width * height];
279
280        // 2D bounds
281        final Rectangle bounds2d = new Rectangle(x, y, width, height);
282
283        for (int i = 1; i < points.size(); i++)
284            drawLine3DInBooleanMask2D(bounds2d, result, z, points.get(i - 1), points.get(i));
285
286        return result;
287    }
288
289    public static void drawLine3DInBooleanMask2D(Rectangle bounds2d, boolean[] result, int z, Point3D p1, Point3D p2)
290    {
291        final Line2D l = new Line2D.Double(p1.getX(), p1.getY(), p2.getX(), p2.getY());
292
293        // 2D intersection ?
294        if (l.intersects(bounds2d))
295        {
296            // 3D intersection ?
297            if (((p1.getZ() <= z) && (p2.getZ() >= z)) || ((p2.getZ() <= z) && (p1.getZ() >= z)))
298            {
299                final int bx = bounds2d.x;
300                final int by = bounds2d.y;
301                final int pitch = bounds2d.width;
302                final Line3DIterator it = new Line3DIterator(new Line3D(p1, p2), 1d);
303
304                while (it.hasNext())
305                {
306                    final Point3D pt = it.next();
307
308                    // same Z ?
309                    if (Math.floor(pt.getZ()) == z)
310                    {
311                        final int x = (int) Math.floor(pt.getX());
312                        final int y = (int) Math.floor(pt.getY());
313
314                        // draw inside the mask
315                        if (bounds2d.contains(x, y))
316                            result[(x - bx) + ((y - by) * pitch)] = true;
317                    }
318                }
319            }
320        }
321    }
322
323    /**
324     * roi changed
325     */
326    @Override
327    public void onChanged(CollapsibleEvent object)
328    {
329        final ROIEvent event = (ROIEvent) object;
330
331        // do here global process on ROI change
332        switch (event.getType())
333        {
334            case ROI_CHANGED:
335                // refresh shape
336                updatePolyline();
337                break;
338
339            case FOCUS_CHANGED:
340                ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
341                break;
342
343            case SELECTION_CHANGED:
344                final boolean s = isSelected();
345
346                // update controls point state given the selection state of the ROI
347                synchronized (controlPoints)
348                {
349                    for (Anchor3D pt : controlPoints)
350                    {
351                        pt.setVisible(s);
352                        if (!s)
353                            pt.setSelected(false);
354                    }
355                }
356
357                ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
358                break;
359
360            case PROPERTY_CHANGED:
361                final String property = event.getPropertyName();
362
363                if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR)
364                        || StringUtil.equals(property, PROPERTY_OPACITY))
365                    ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties();
366                break;
367
368            default:
369                break;
370        }
371
372        super.onChanged(object);
373    }
374
375    @Override
376    public double computeNumberOfPoints()
377    {
378        return 0d;
379    }
380
381    @Override
382    public boolean contains(ROI roi)
383    {
384        return false;
385    }
386
387    protected void updatePolyline()
388    {
389        final int len = controlPoints.size();
390        final double ptsX[] = new double[len];
391        final double ptsY[] = new double[len];
392        final double ptsZ[] = new double[len];
393
394        for (int i = 0; i < len; i++)
395        {
396            final Anchor3D pt = controlPoints.get(i);
397
398            ptsX[i] = pt.getX();
399            ptsY[i] = pt.getY();
400            ptsZ[i] = pt.getZ();
401        }
402
403        final Polyline3D polyline3d = getPolyline3D();
404
405        // we can have a problem here if we try to redraw while we are modifying the polygon points
406        synchronized (polyline3d)
407        {
408            polyline3d.npoints = len;
409            polyline3d.xpoints = ptsX;
410            polyline3d.ypoints = ptsY;
411            polyline3d.zpoints = ptsZ;
412            polyline3d.calculateLines();
413        }
414
415        // the shape should have been rebuilt here
416        ((ROI3DPolyLinePainter) painter).needRebuild = true;
417    }
418
419    @Override
420    public boolean loadFromXML(Node node)
421    {
422        beginUpdate();
423        try
424        {
425            if (!super.loadFromXML(node))
426                return false;
427
428            removeAllPoint();
429
430            final ArrayList<Node> nodesPoint = XMLUtil.getChildren(XMLUtil.getElement(node, ID_POINTS), ID_POINT);
431            if (nodesPoint != null)
432            {
433                for (Node n : nodesPoint)
434                {
435                    final Anchor3D pt = createAnchor(new Point3D.Double());
436                    pt.loadPositionFromXML(n);
437                    addPoint(pt);
438                }
439            }
440        }
441        finally
442        {
443            endUpdate();
444        }
445
446        return true;
447    }
448
449    @Override
450    public boolean saveToXML(Node node)
451    {
452        if (!super.saveToXML(node))
453            return false;
454
455        final Element dependances = XMLUtil.setElement(node, ID_POINTS);
456        for (Anchor3D pt : controlPoints)
457            pt.savePositionToXML(XMLUtil.addElement(dependances, ID_POINT));
458
459        return true;
460    }
461}