001/**
002 * 
003 */
004package plugins.kernel.roi.roi3d;
005
006import icy.canvas.IcyCanvas;
007import icy.canvas.IcyCanvas2D;
008import icy.common.CollapsibleEvent;
009import icy.painter.Anchor3D;
010import icy.painter.OverlayEvent;
011import icy.painter.OverlayEvent.OverlayEventType;
012import icy.resource.ResourceUtil;
013import icy.roi.ROI;
014import icy.roi.ROIEvent;
015import icy.sequence.Sequence;
016import icy.type.geom.Line3D;
017import icy.type.point.Point3D;
018import icy.type.point.Point5D;
019import icy.util.GraphicsUtil;
020import icy.util.StringUtil;
021import icy.util.XMLUtil;
022import icy.vtk.IcyVtkPanel;
023
024import java.awt.Color;
025import java.awt.Graphics2D;
026import java.awt.Rectangle;
027import java.awt.geom.Ellipse2D;
028import java.awt.geom.Rectangle2D;
029
030import org.w3c.dom.Node;
031
032import plugins.kernel.canvas.VtkCanvas;
033import vtk.vtkActor;
034import vtk.vtkPolyDataMapper;
035import vtk.vtkSphereSource;
036
037/**
038 * ROI 3D Point class.<br>
039 * 
040 * @author Stephane Dallongeville
041 */
042public class ROI3DPoint extends ROI3DShape
043{
044    public class ROI3DPointPainter extends ROI3DShapePainter
045    {
046        vtkSphereSource vtkSource;
047
048        @Override
049        protected void finalize() throws Throwable
050        {
051            super.finalize();
052
053            // release extra VTK objects
054            if (vtkSource != null)
055                vtkSource.Delete();
056        }
057
058        @Override
059        protected boolean isSmall(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
060        {
061            if (isSelected())
062                return false;
063
064            return true;
065        }
066
067        @Override
068        protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
069        {
070            if (isSelected())
071                return false;
072
073            return true;
074        }
075
076        @Override
077        public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
078        {
079            if (canvas instanceof IcyCanvas2D)
080            {
081                final Graphics2D g2 = (Graphics2D) g.create();
082
083                if (isSelected() && !isReadOnly())
084                {
085                    // draw control point if selected
086                    synchronized (controlPoints)
087                    {
088                        for (Anchor3D pt : controlPoints)
089                            pt.paint(g2, sequence, canvas);
090                    }
091                }
092                else
093                {
094                    final Point3D pos = getPoint();
095                    final double ray = getAdjustedStroke(canvas);
096                    final Ellipse2D ellipse = new Ellipse2D.Double(pos.getX() - ray, pos.getY() - ray, ray * 2,
097                            ray * 2);
098
099                    // get canvas Z position
100                    final int cnvZ = canvas.getPositionZ();
101                    // calculate z fade range
102                    final double zRange = Math.min(10d, Math.max(3d, sequence.getSizeZ() / 8d));
103
104                    // get Z pos
105                    final double z = pos.getZ();
106                    // get delta Z (difference between canvas Z position and point Z pos)
107                    final double dz = Math.abs(z - cnvZ);
108
109                    // not visible on this Z position
110                    if (dz > zRange)
111                        return;
112
113                    // ratio for size / opacity
114                    final float ratio = 1f - (float) (dz / zRange);
115
116                    if (ratio != 1f)
117                        GraphicsUtil.mixAlpha(g2, ratio);
118
119                    // draw shape
120                    g2.setColor(getDisplayColor());
121                    g2.fill(ellipse);
122                }
123
124                g2.dispose();
125            }
126            else
127                // just use parent method
128                super.drawROI(g, sequence, canvas);
129        }
130
131        @Override
132        protected void initVtkObjects()
133        {
134            super.initVtkObjects();
135            
136            // init 3D painters stuff
137            vtkSource = new vtkSphereSource();
138            vtkSource.SetRadius(getStroke());
139            vtkSource.SetThetaResolution(12);
140            vtkSource.SetPhiResolution(12);
141
142            // delete previously created objects that we will recreate
143            if (actor != null)
144                actor.Delete();
145            if (polyMapper != null)
146                polyMapper.Delete();
147            
148            polyMapper = new vtkPolyDataMapper();
149            polyMapper.SetInputConnection((vtkSource).GetOutputPort());
150
151            actor = new vtkActor();
152            actor.SetMapper(polyMapper);
153
154            // initialize color
155            final Color col = getColor();
156            actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d);
157        }
158
159        /**
160         * update 3D painter for 3D canvas (called only when VTK is loaded).
161         */
162        @Override
163        protected void rebuildVtkObjects()
164        {
165            final VtkCanvas canvas = canvas3d.get();
166            // canvas was closed
167            if (canvas == null)
168                return;
169
170            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
171            // canvas was closed
172            if (vtkPanel == null)
173                return;
174
175            final Sequence seq = canvas.getSequence();
176            // nothing to update
177            if (seq == null)
178                return;
179
180            final Point3D pos = getPoint();
181
182            // actor can be accessed in canvas3d for rendering so we need to synchronize access
183            vtkPanel.lock();
184            try
185            {
186                // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid)
187                vtkSource.SetRadius(getStroke() * scaling[0]);
188                vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], pos.getZ() * scaling[2]);
189                polyMapper.Update();
190
191                // vtkSource.SetRadius(getStroke());
192                // vtkSource.SetCenter(pos.getX(), pos.getY(), curZ);
193                // polyMapper.Update();
194                // actor.SetScale(scaling);
195            }
196            finally
197            {
198                vtkPanel.unlock();
199            }
200
201            // need to repaint
202            painterChanged();
203        }
204
205        @Override
206        protected void updateVtkDisplayProperties()
207        {
208            if (actor == null)
209                return;
210
211            final VtkCanvas cnv = canvas3d.get();
212            final Color col = getDisplayColor();
213            final double r = col.getRed() / 255d;
214            final double g = col.getGreen() / 255d;
215            final double b = col.getBlue() / 255d;
216            // final float opacity = getOpacity();
217
218            final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
219
220            // we need to lock canvas as actor can be accessed during rendering
221            if (vtkPanel != null)
222                vtkPanel.lock();
223            try
224            {
225                actor.GetProperty().SetColor(r, g, b);
226            }
227            finally
228            {
229                if (vtkPanel != null)
230                    vtkPanel.unlock();
231            }
232
233            // need to repaint
234            painterChanged();
235        }
236    }
237
238    public static final String ID_POSITION = "position";
239
240    private final Anchor3D position;
241
242    /**
243     * @deprecated
244     */
245    @Deprecated
246    public ROI3DPoint(Point3D pt, boolean cm)
247    {
248        this(pt);
249    }
250
251    public ROI3DPoint(Point3D position)
252    {
253        super(new Line3D());
254
255        this.position = createAnchor(position);
256        this.position.setSelected(true);
257        addPoint(this.position);
258
259        // set icon
260        setIcon(ResourceUtil.ICON_ROI_POINT);
261    }
262
263    /**
264     * Generic constructor for interactive mode
265     */
266    public ROI3DPoint(Point5D pt)
267    {
268        this(pt.toPoint3D());
269    }
270
271    public ROI3DPoint(double x, double y, double z)
272    {
273        this(new Point3D.Double(x, y, z));
274    }
275
276    public ROI3DPoint()
277    {
278        this(new Point3D.Double());
279    }
280
281    @Override
282    public String getDefaultName()
283    {
284        return "Point3D";
285    }
286
287    @Override
288    protected ROI3DShapePainter createPainter()
289    {
290        return new ROI3DPointPainter();
291    }
292
293    public Line3D getLine()
294    {
295        return (Line3D) shape;
296    }
297
298    public Point3D getPoint()
299    {
300        return position.getPosition();
301    }
302
303    /**
304     * Called when anchor overlay changed
305     */
306    @Override
307    public void controlPointOverlayChanged(OverlayEvent event)
308    {
309        // we only mind about painter change from anchor...
310        if (event.getType() == OverlayEventType.PAINTER_CHANGED)
311        {
312            // here we want to have ROI focused when point is selected (special case for ROIPoint)
313            if (hasSelectedPoint())
314                setFocused(true);
315
316            // anchor changed --> ROI painter changed
317            getOverlay().painterChanged();
318        }
319    }
320    
321    @Override
322    public boolean contains(ROI roi)
323    {
324        return false;
325    }
326
327    @Override
328    public boolean intersects(ROI r)
329    {
330        // special case of ROI3DPoint
331        if (r instanceof ROI3DPoint)
332            return onSamePos(((ROI3DPoint) r), false) && ((ROI3DPoint) r).getPoint().equals(getPoint());
333
334        return super.intersects(r);
335    }
336
337    @Override
338    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive)
339    {
340        if ((width <= 0) || (height <= 0))
341            return new boolean[0];
342
343        final boolean[] result = new boolean[width * height];
344        // 2D bounds
345        final Rectangle bounds2d = new Rectangle(x, y, width, height);
346
347        final Point3D pos = getPoint();
348
349        // same Z ?
350        if (Math.floor(pos.getZ()) == z)
351        {
352            // inside the mask ?
353            if (bounds2d.contains(pos.toPoint2D()))
354            {
355                final int px = (int) Math.floor(pos.getX());
356                final int py = (int) Math.floor(pos.getY());
357
358                // set the pixel
359                result[(px - x) + ((py - y) * width)] = true;
360            }
361        }
362
363        return result;
364    }
365
366    /**
367     * roi changed
368     */
369    @Override
370    public void onChanged(CollapsibleEvent object)
371    {
372        final ROIEvent event = (ROIEvent) object;
373
374        // do here global process on ROI change
375        switch (event.getType())
376        {
377            case PROPERTY_CHANGED:
378                final String property = event.getPropertyName();
379
380                // stroke changed --> rebuild vtk object
381                if (StringUtil.equals(property, PROPERTY_STROKE))
382                    ((ROI3DShapePainter) getOverlay()).needRebuild = true;
383                break;
384
385            case SELECTION_CHANGED:
386                // always select the control point when ROI was just selected
387                if (isSelected())
388                    position.setSelected(true);
389                break;
390
391            default:
392                break;
393        }
394
395        super.onChanged(object);
396    }
397
398    @Override
399    protected void updateShape()
400    {
401        final Point3D pt = getPoint();
402        final double x = pt.getX();
403        final double y = pt.getY();
404        final double z = pt.getZ();
405
406        getLine().setLine(x, y, z, x, y, z);
407
408        // call super method after shape has been updated
409        super.updateShape();
410    }
411
412    @Override
413    public boolean canAddPoint()
414    {
415        // this ROI doesn't support point add
416        return false;
417    }
418
419    @Override
420    protected boolean removePoint(IcyCanvas canvas, Anchor3D pt)
421    {
422        if (canvas != null)
423        {
424            // remove point on this ROI remove the ROI from current sequence
425            canvas.getSequence().removeROI(this);
426            return true;
427        }
428
429        return false;
430    }
431
432    @Override
433    public double computeNumberOfContourPoints()
434    {
435        return 0d;
436    }
437
438    @Override
439    public double computeNumberOfPoints()
440    {
441        return 0d;
442    }
443
444    @Override
445    public boolean loadFromXML(Node node)
446    {
447        beginUpdate();
448        try
449        {
450            if (!super.loadFromXML(node))
451                return false;
452
453            position.loadPositionFromXML(XMLUtil.getElement(node, ID_POSITION));
454        }
455        finally
456        {
457            endUpdate();
458        }
459
460        return true;
461    }
462
463    @Override
464    public boolean saveToXML(Node node)
465    {
466        if (!super.saveToXML(node))
467            return false;
468
469        position.savePositionToXML(XMLUtil.setElement(node, ID_POSITION));
470
471        return true;
472    }
473}