001/**
002 * 
003 */
004package plugins.kernel.roi.roi3d;
005
006import icy.canvas.IcyCanvas;
007import icy.math.Line3DIterator;
008import icy.painter.Anchor3D;
009import icy.resource.ResourceUtil;
010import icy.roi.ROI;
011import icy.sequence.Sequence;
012import icy.type.geom.Line3D;
013import icy.type.point.Point3D;
014import icy.type.point.Point5D;
015import icy.type.rectangle.Rectangle3D;
016import icy.util.XMLUtil;
017import icy.vtk.IcyVtkPanel;
018
019import java.awt.Graphics2D;
020import java.awt.Rectangle;
021import java.awt.geom.Line2D;
022import java.awt.geom.Rectangle2D;
023import java.util.List;
024
025import org.w3c.dom.Node;
026
027import plugins.kernel.canvas.VtkCanvas;
028import vtk.vtkTubeFilter;
029
030/**
031 * ROI 3D Line.
032 * 
033 * @author Stephane Dallongeville
034 */
035public class ROI3DLine extends ROI3DShape
036{
037    public class ROI3DLinePainter extends ROI3DShapePainter
038    {
039        // extra VTK 3D objects
040        protected vtkTubeFilter tubeFilter;
041
042        public ROI3DLinePainter()
043        {
044            super();
045
046            // don't create VTK object on constructor
047            tubeFilter = null;
048        }
049
050        @Override
051        protected void finalize() throws Throwable
052        {
053            super.finalize();
054
055            // release allocated VTK resources
056            if (tubeFilter != null)
057                tubeFilter.Delete();
058        };
059
060        @Override
061        protected void initVtkObjects()
062        {
063            super.initVtkObjects();
064
065            // init specific tube filter
066            tubeFilter = new vtkTubeFilter();
067            tubeFilter.SetInputData(polyData);
068            tubeFilter.SetRadius(1d);
069            tubeFilter.CappingOn();
070            tubeFilter.SetNumberOfSides(8);
071            // tubeFilter.SidesShareVerticesOff();
072            polyMapper.SetInputConnection(tubeFilter.GetOutputPort());
073        }
074
075        @Override
076        protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
077        {
078            if (isSelected())
079                return false;
080
081            return super.isTiny(bounds, g, canvas);
082        }
083
084        /**
085         * update 3D painter for 3D canvas (called only when VTK is loaded).
086         */
087        @Override
088        protected void rebuildVtkObjects()
089        {
090            super.rebuildVtkObjects();
091
092            final VtkCanvas canvas = canvas3d.get();
093            // canvas was closed
094            if (canvas == null)
095                return;
096
097            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
098            // canvas was closed
099            if (vtkPanel == null)
100                return;
101
102            // sub VTK object not yet initialized (it can happen, have to check why ??)
103            if (tubeFilter == null)
104                return;
105
106            // actor can be accessed in canvas3d for rendering so we need to synchronize access
107            vtkPanel.lock();
108            try
109            {
110                // just be sure the tube filter is also up to date
111                tubeFilter.Update();
112            }
113            finally
114            {
115                vtkPanel.unlock();
116            }
117        }
118
119        protected void updateVtkTubeRadius()
120        {
121            final VtkCanvas canvas = canvas3d.get();
122            // canvas was closed
123            if (canvas == null)
124                return;
125
126            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
127            // canvas was closed
128            if (vtkPanel == null)
129                return;
130
131            // sub VTK object not yet initialized (it can happen, have to check why ??)
132            if (tubeFilter == null)
133                return;
134
135            // update tube radius base on canvas scale X and image scale X
136            final double radius = canvas.canvasToImageLogDeltaX((int) getStroke()) * scaling[0];
137
138            if (tubeFilter.GetRadius() != radius)
139            {
140                // actor can be accessed in canvas3d for rendering so we need to synchronize access
141                vtkPanel.lock();
142                try
143                {
144                    tubeFilter.SetRadius(radius);
145                    tubeFilter.Update();
146                }
147                finally
148                {
149                    vtkPanel.unlock();
150                }
151
152                // need to repaint
153                painterChanged();
154            }
155        }
156
157        @Override
158        public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
159        {
160            super.drawROI(g, sequence, canvas);
161
162            // update VTK tube radius if needed
163            if (canvas instanceof VtkCanvas)
164                updateVtkTubeRadius();
165        }
166
167        @Override
168        protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
169        {
170            drawShape(g, sequence, canvas, simplified, false);
171        }
172    }
173
174    public static final String ID_PT1 = "pt1";
175    public static final String ID_PT2 = "pt2";
176
177    protected final Anchor3D pt1;
178    protected final Anchor3D pt2;
179
180    public ROI3DLine(Point3D pt1, Point3D pt2)
181    {
182        super(new Line3D());
183
184        this.pt1 = createAnchor(pt1);
185        this.pt2 = createAnchor(pt2);
186        // keep pt2 selected to size the line for "interactive mode"
187        this.pt2.setSelected(true);
188
189        addPoint(this.pt1);
190        addPoint(this.pt2);
191
192        // set icon
193        setIcon(ResourceUtil.ICON_ROI_LINE);
194    }
195
196    public ROI3DLine(Line3D line)
197    {
198        this(line.getP1(), line.getP2());
199    }
200
201    public ROI3DLine(Point3D pt)
202    {
203        this(new Point3D.Double(pt.getX(), pt.getY(), pt.getZ()), pt);
204    }
205
206    /**
207     * Generic constructor for interactive mode
208     */
209    public ROI3DLine(Point5D pt)
210    {
211        this(pt.toPoint3D());
212        // getOverlay().setMousePos(pt);
213    }
214
215    public ROI3DLine(double x1, double y1, double z1, double x2, double y2, double z2)
216    {
217        this(new Point3D.Double(x1, y1, z1), new Point3D.Double(x2, y2, z2));
218    }
219
220    public ROI3DLine()
221    {
222        this(new Point3D.Double(), new Point3D.Double());
223    }
224
225    @Override
226    public String getDefaultName()
227    {
228        return "Line3D";
229    }
230
231    @Override
232    protected ROI3DShapePainter createPainter()
233    {
234        return new ROI3DLinePainter();
235    }
236
237    public Line3D getLine()
238    {
239        return (Line3D) shape;
240    }
241
242    @Override
243    protected void updateShape()
244    {
245        getLine().setLine(pt1.getPosition(), pt2.getPosition());
246
247        // call super method after shape has been updated
248        super.updateShape();
249    }
250
251    @Override
252    public boolean canAddPoint()
253    {
254        // this ROI doesn't support point add
255        return false;
256    }
257
258    @Override
259    public boolean canRemovePoint()
260    {
261        // this ROI doesn't support point remove
262        return false;
263    }
264
265    @Override
266    protected boolean removePoint(IcyCanvas canvas, Anchor3D pt)
267    {
268        // this ROI doesn't support point remove
269        return false;
270    }
271
272    @Override
273    public boolean canSetBounds()
274    {
275        return true;
276    }
277
278    @Override
279    public void setBounds3D(Rectangle3D bounds)
280    {
281        beginUpdate();
282        try
283        {
284            pt1.setPosition(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
285            pt2.setPosition(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ());
286        }
287        finally
288        {
289            endUpdate();
290        }
291    }
292
293    public void setLine(Line3D line)
294    {
295        setBounds3D(line.getBounds());
296    }
297
298    @Override
299    protected double getTotalDistance(List<Point3D> points, double factorX, double factorY, double factorZ)
300    {
301        // for line the total length don't need last point connection
302        return Point3D.getTotalDistance(points, factorX, factorY, factorZ, false);
303    }
304
305    @Override
306    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
307    {
308        if ((width <= 0) || (height <= 0))
309            return new boolean[0];
310
311        final boolean[] result = new boolean[width * height];
312        // 2D bounds
313        final Rectangle bounds2d = new Rectangle(x, y, width, height);
314
315        drawLine3DInBooleanMask2D(bounds2d, result, z, pt1.getPositionInternal(), pt2.getPositionInternal());
316
317        return result;
318    }
319
320    public static void drawLine3DInBooleanMask2D(Rectangle bounds2d, boolean[] result, int z, Point3D p1, Point3D p2)
321    {
322        final Line2D l = new Line2D.Double(p1.getX(), p1.getY(), p2.getX(), p2.getY());
323
324        // 2D intersection ?
325        if (l.intersects(bounds2d))
326        {
327            // 3D intersection ?
328            if (((p1.getZ() <= z) && (p2.getZ() >= z)) || ((p2.getZ() <= z) && (p1.getZ() >= z)))
329            {
330                final int bx = bounds2d.x;
331                final int by = bounds2d.y;
332                final int pitch = bounds2d.width;
333                final Line3DIterator it = new Line3DIterator(new Line3D(p1, p2), 1d);
334
335                while (it.hasNext())
336                {
337                    final Point3D pt = it.next();
338
339                    // same Z ?
340                    if (Math.floor(pt.getZ()) == z)
341                    {
342                        final int x = (int) Math.floor(pt.getX());
343                        final int y = (int) Math.floor(pt.getY());
344
345                        // draw inside the mask
346                        if (bounds2d.contains(x, y))
347                            result[(x - bx) + ((y - by) * pitch)] = true;
348                    }
349                }
350            }
351        }
352    }
353
354    @Override
355    public double computeNumberOfPoints()
356    {
357        return 0d;
358    }
359
360    @Override
361    public boolean contains(ROI roi)
362    {
363        return false;
364    }
365
366    @Override
367    public boolean intersects(ROI r)
368    {
369        // special case of ROI3DLine
370        if (r instanceof ROI3DLine)
371            return onSamePos(((ROI3DLine) r), false) && ((ROI3DLine) r).getLine().intersectsLine(getLine());
372
373        return super.intersects(r);
374    }
375
376    @Override
377    public boolean loadFromXML(Node node)
378    {
379        beginUpdate();
380        try
381        {
382            if (!super.loadFromXML(node))
383                return false;
384
385            pt1.loadPositionFromXML(XMLUtil.getElement(node, ID_PT1));
386            pt2.loadPositionFromXML(XMLUtil.getElement(node, ID_PT2));
387        }
388        finally
389        {
390            endUpdate();
391        }
392
393        return true;
394    }
395
396    @Override
397    public boolean saveToXML(Node node)
398    {
399        if (!super.saveToXML(node))
400            return false;
401
402        pt1.savePositionToXML(XMLUtil.setElement(node, ID_PT1));
403        pt2.savePositionToXML(XMLUtil.setElement(node, ID_PT2));
404
405        return true;
406    }
407}