001/**
002 * 
003 */
004package plugins.kernel.roi.roi3d;
005
006import icy.canvas.IcyCanvas;
007import icy.painter.VtkPainter;
008import icy.sequence.Sequence;
009import icy.system.thread.ThreadUtil;
010import icy.type.point.Point5D;
011import icy.type.rectangle.Rectangle3D;
012import icy.vtk.IcyVtkPanel;
013import icy.vtk.VtkUtil;
014
015import java.awt.Color;
016import java.awt.Graphics2D;
017import java.awt.event.InputEvent;
018import java.awt.geom.PathIterator;
019import java.lang.ref.WeakReference;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.List;
023
024import plugins.kernel.canvas.VtkCanvas;
025import plugins.kernel.roi.roi2d.ROI2DShape;
026import vtk.vtkActor;
027import vtk.vtkCellArray;
028import vtk.vtkInformation;
029import vtk.vtkPoints;
030import vtk.vtkPolyData;
031import vtk.vtkPolyDataMapper;
032import vtk.vtkProp;
033import vtk.vtkProperty;
034
035/**
036 * Base class defining a generic 3D Shape ROI as a stack of individual 2D Shape ROI.
037 * 
038 * @author Stephane
039 */
040public abstract class ROI3DStackShape extends ROI3DStack<ROI2DShape>
041{
042    public ROI3DStackShape(Class<? extends ROI2DShape> roiClass)
043    {
044        super(roiClass);
045    }
046
047    @Override
048    protected ROIPainter createPainter()
049    {
050        return new ROI3DStackShapePainter();
051    }
052
053    @Override
054    public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z)
055    {
056        final ROI2DShape slice = getSlice((int) z);
057
058        if (slice != null)
059            return slice.isOverEdge(canvas, x, y);
060
061        return false;
062    }
063
064    public class ROI3DStackShapePainter extends ROI3DStackPainter implements VtkPainter, Runnable
065    {
066        // VTK 3D objects
067        protected vtkPolyData outline;
068        protected vtkPolyDataMapper outlineMapper;
069        protected vtkActor outlineActor;
070        protected vtkInformation vtkInfo;
071        protected vtkCellArray vCells;
072        protected vtkPoints vPoints;
073        protected vtkPolyData polyData;
074        protected vtkPolyDataMapper polyMapper;
075        protected vtkActor actor;
076        // 3D internal
077        protected boolean needRebuild;
078        protected double scaling[];
079        protected WeakReference<VtkCanvas> canvas3d;
080
081        public ROI3DStackShapePainter()
082        {
083            super();
084
085            // don't create VTK object on constructor
086            outline = null;
087            outlineMapper = null;
088            outlineActor = null;
089            vtkInfo = null;
090            vCells = null;
091            vPoints = null;
092            polyData = null;
093            polyMapper = null;
094            actor = null;
095
096            scaling = new double[3];
097            Arrays.fill(scaling, 1d);
098
099            needRebuild = true;
100            canvas3d = new WeakReference<VtkCanvas>(null);
101        }
102
103        @Override
104        protected void finalize() throws Throwable
105        {
106            super.finalize();
107
108            // release allocated VTK resources
109            if (actor != null)
110                actor.Delete();
111            if (polyMapper != null)
112                polyMapper.Delete();
113            if (polyData != null)
114                polyData.Delete();
115            if (vPoints != null)
116                vPoints.Delete();
117            if (vCells != null)
118                vCells.Delete();
119            if (outlineActor != null)
120            {
121                outlineActor.SetPropertyKeys(null);
122                outlineActor.Delete();
123            }
124            if (vtkInfo != null)
125            {
126                vtkInfo.Remove(VtkCanvas.visibilityKey);
127                vtkInfo.Delete();
128            }
129            if (outlineMapper != null)
130                outlineMapper.Delete();
131            if (outline != null)
132            {
133                outline.GetPointData().GetScalars().Delete();
134                outline.GetPointData().Delete();
135                outline.Delete();
136            }
137        };
138
139        protected void initVtkObjects()
140        {
141            outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d);
142            outlineMapper = new vtkPolyDataMapper();
143            outlineActor = new vtkActor();
144            outlineActor.SetMapper(outlineMapper);
145            // disable picking on the outline
146            outlineActor.SetPickable(0);
147            // and set it to wireframe representation
148            outlineActor.GetProperty().SetRepresentationToWireframe();
149            // use vtkInformations to store outline visibility state (hacky)
150            vtkInfo = new vtkInformation();
151            vtkInfo.Set(VtkCanvas.visibilityKey, 0);
152            // VtkCanvas use this to restore correctly outline visibility flag
153            outlineActor.SetPropertyKeys(vtkInfo);
154
155            // init poly data object
156            polyData = new vtkPolyData();
157            polyMapper = new vtkPolyDataMapper();
158            polyMapper.SetInputData(polyData);
159            actor = new vtkActor();
160            actor.SetMapper(polyMapper);
161
162            // initialize color and stroke
163            final Color col = getColor();
164            final double r = col.getRed() / 255d;
165            final double g = col.getGreen() / 255d;
166            final double b = col.getBlue() / 255d;
167
168            outlineActor.GetProperty().SetColor(r, g, b);
169            final vtkProperty property = actor.GetProperty();
170            property.SetPointSize(getStroke());
171            property.SetColor(r, g, b);
172        }
173
174        /**
175         * update 3D painter for 3D canvas (called only when VTK is loaded).
176         */
177        protected void rebuildVtkObjects()
178        {
179            final VtkCanvas canvas = canvas3d.get();
180            // canvas was closed
181            if (canvas == null)
182                return;
183
184            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
185            // canvas was closed
186            if (vtkPanel == null)
187                return;
188
189            final Sequence seq = canvas.getSequence();
190            // nothing to update
191            if (seq == null)
192                return;
193
194            // get bounds
195            final Rectangle3D bounds = getBounds3D();
196
197            // update outline
198            VtkUtil.setOutlineBounds(outline, bounds.getMinX() * scaling[0], bounds.getMaxX() * scaling[0],
199                    bounds.getMinY() * scaling[1], bounds.getMaxY() * scaling[1], bounds.getMinZ() * scaling[2],
200                    bounds.getMaxZ() * scaling[2], canvas);
201
202            // update polydata object
203            final List<double[]> point3DList = new ArrayList<double[]>();
204            final List<int[]> polyList = new ArrayList<int[]>();
205            final double[] coords = new double[6];
206
207            // starting position
208            double xm = 0d;
209            double ym = 0d;
210            double x0 = 0d;
211            double y0 = 0d;
212            double x1 = 0d;
213            double y1 = 0d;
214            double xs = scaling[0];
215            double ys = scaling[1];
216            int ind;
217
218            for (double z = bounds.getMinZ(); z <= bounds.getMaxZ(); z += 1d)
219            {
220                // get ROI shape for this slice
221                final ROI2DShape roi2dShape = getSlice((int) z);
222
223                // no ROI here --> continue
224                if (roi2dShape == null)
225                    continue;
226
227                final double z0 = (z + 0d) * scaling[2];
228                final double z1 = (z + 1d) * scaling[2];
229
230                // use flat path
231                final PathIterator path = roi2dShape.getPathIterator(null, 0.5d);
232
233                // build point data
234                while (!path.isDone())
235                {
236                    switch (path.currentSegment(coords))
237                    {
238                        case PathIterator.SEG_MOVETO:
239                            x0 = xm = coords[0] * xs;
240                            y0 = ym = coords[1] * ys;
241                            break;
242
243                        case PathIterator.SEG_LINETO:
244                            x1 = coords[0] * xs;
245                            y1 = coords[1] * ys;
246
247                            ind = point3DList.size();
248
249                            point3DList.add(new double[] {x0, y0, z0});
250                            point3DList.add(new double[] {x1, y1, z0});
251                            point3DList.add(new double[] {x0, y0, z1});
252                            point3DList.add(new double[] {x1, y1, z1});
253                            polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind});
254                            polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind});
255
256                            x0 = x1;
257                            y0 = y1;
258                            break;
259
260                        case PathIterator.SEG_CLOSE:
261                            x1 = xm;
262                            y1 = ym;
263
264                            ind = point3DList.size();
265
266                            point3DList.add(new double[] {x0, y0, z0});
267                            point3DList.add(new double[] {x1, y1, z0});
268                            point3DList.add(new double[] {x0, y0, z1});
269                            point3DList.add(new double[] {x1, y1, z1});
270                            polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind});
271                            polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind});
272
273                            x0 = x1;
274                            y0 = y1;
275                            break;
276                    }
277
278                    path.next();
279                }
280            }
281
282            // convert to array
283            final double[][] vertices = new double[point3DList.size()][3];
284            final int[][] indexes = new int[polyList.size()][3];
285
286            ind = 0;
287            for (double[] pt3D : point3DList)
288                vertices[ind++] = pt3D;
289
290            ind = 0;
291            for (int[] poly : polyList)
292                indexes[ind++] = poly;
293
294            final vtkCellArray previousCells = vCells;
295            final vtkPoints previousPoints = vPoints;
296            vCells = VtkUtil.getCells(polyList.size(), VtkUtil.prepareCells(indexes));
297            vPoints = VtkUtil.getPoints(vertices);
298
299            // actor can be accessed in canvas3d for rendering so we need to synchronize access
300            vtkPanel.lock();
301            try
302            {
303                // update outline polygon data
304                outlineMapper.SetInputData(outline);
305                outlineMapper.Update();
306                // update polygon data from cell and points
307                polyData.SetPolys(vCells);
308                polyData.SetPoints(vPoints);
309                polyMapper.Update();
310
311                // release previous allocated VTK objects
312                if (previousCells != null)
313                    previousCells.Delete();
314                if (previousPoints != null)
315                    previousPoints.Delete();
316            }
317            finally
318            {
319                vtkPanel.unlock();
320            }
321
322            // update color and others properties
323            updateVtkDisplayProperties();
324        }
325
326        protected void updateVtkDisplayProperties()
327        {
328            if (actor == null)
329                return;
330
331            final VtkCanvas cnv = canvas3d.get();
332            final vtkProperty vtkProperty = actor.GetProperty();
333            final Color col = getDisplayColor();
334            final double r = col.getRed() / 255d;
335            final double g = col.getGreen() / 255d;
336            final double b = col.getBlue() / 255d;
337            final double strk = getStroke();
338            // final float opacity = getOpacity();
339
340            final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
341
342            // we need to lock canvas as actor can be accessed during rendering
343            if (vtkPanel != null)
344                vtkPanel.lock();
345            try
346            {
347                // set actors color
348                outlineActor.GetProperty().SetColor(r, g, b);
349                if (isSelected())
350                {
351                    outlineActor.GetProperty().SetRepresentationToWireframe();
352                    outlineActor.SetVisibility(1);
353                    vtkInfo.Set(VtkCanvas.visibilityKey, 1);
354                }
355                else
356                {
357                    outlineActor.GetProperty().SetRepresentationToPoints();
358                    outlineActor.SetVisibility(0);
359                    vtkInfo.Set(VtkCanvas.visibilityKey, 0);
360                }
361                vtkProperty.SetColor(r, g, b);
362                vtkProperty.SetPointSize(strk);
363                // opacity here is about ROI content, global opacity is handled by Layer
364                // vtkProperty.SetOpacity(opacity);
365                setVtkObjectsColor(col);
366            }
367            finally
368            {
369                if (vtkPanel != null)
370                    vtkPanel.unlock();
371            }
372
373            // need to repaint
374            painterChanged();
375        }
376
377        protected void setVtkObjectsColor(Color color)
378        {
379            if (outline != null)
380                VtkUtil.setPolyDataColor(outline, color, canvas3d.get());
381            if (polyData != null)
382                VtkUtil.setPolyDataColor(polyData, color, canvas3d.get());
383        }
384
385        @Override
386        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
387        {
388            if (isActiveFor(canvas))
389            {
390                if (canvas instanceof VtkCanvas)
391                {
392                    // 3D canvas
393                    final VtkCanvas cnv = (VtkCanvas) canvas;
394                    // update reference if needed
395                    if (canvas3d.get() != cnv)
396                        canvas3d = new WeakReference<VtkCanvas>(cnv);
397
398                    // FIXME : need a better implementation
399                    final double[] s = cnv.getVolumeScale();
400
401                    // scaling changed ?
402                    if (!Arrays.equals(scaling, s))
403                    {
404                        // update scaling
405                        scaling = s;
406                        // need rebuild
407                        needRebuild = true;
408                    }
409
410                    // need to rebuild 3D data structures ?
411                    if (needRebuild)
412                    {
413                        // initialize VTK objects if not yet done
414                        if (actor == null)
415                            initVtkObjects();
416
417                        // request rebuild 3D objects
418                        ThreadUtil.runSingle(this);
419                        needRebuild = false;
420                    }
421                }
422                else
423                    super.paint(g, sequence, canvas);
424            }
425        }
426
427        @Override
428        protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
429        {
430            // specific VTK canvas processing
431            if (canvas instanceof VtkCanvas)
432            {
433                // mouse is over the ROI actor ? --> focus the ROI
434                final boolean focused = (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject());
435
436                setFocused(focused);
437
438                return focused;
439            }
440
441            return super.updateFocus(e, imagePoint, canvas);
442        }
443
444        @Override
445        public vtkProp[] getProps()
446        {
447            // initialize VTK objects if not yet done
448            if (actor == null)
449                initVtkObjects();
450
451            return new vtkActor[] {actor, outlineActor};
452        }
453
454        @Override
455        public void run()
456        {
457            rebuildVtkObjects();
458        }
459    }
460}