001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.vtk;
020
021import java.awt.event.KeyEvent;
022import java.awt.event.KeyListener;
023import java.awt.event.MouseEvent;
024import java.awt.event.MouseListener;
025import java.awt.event.MouseMotionListener;
026import java.awt.event.MouseWheelEvent;
027import java.awt.event.MouseWheelListener;
028
029import icy.preferences.CanvasPreferences;
030import icy.system.thread.ThreadUtil;
031import icy.util.EventUtil;
032import vtk.vtkActor;
033import vtk.vtkActorCollection;
034import vtk.vtkAxesActor;
035import vtk.vtkCamera;
036import vtk.vtkCellPicker;
037import vtk.vtkLight;
038import vtk.vtkPicker;
039import vtk.vtkProp;
040import vtk.vtkRenderer;
041
042/**
043 * Icy custom VTK panel used for VTK rendering.
044 * 
045 * @author stephane dallongeville
046 */
047public class IcyVtkPanel extends VtkJoglPanel
048        implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener, Runnable
049{
050    /**
051     * 
052     */
053    private static final long serialVersionUID = -8455671369400627703L;
054
055    protected Thread renderingMonitor;
056    // protected vtkPropPicker picker;
057    protected vtkCellPicker picker;
058    protected vtkAxesActor axis;
059    protected vtkRenderer axisRenderer;
060    protected vtkCamera axisCam;
061    protected int axisOffset[];
062    protected double axisScale;
063    protected boolean lightFollowCamera;
064    protected volatile long fineRenderingTime;
065
066    public IcyVtkPanel()
067    {
068        super();
069
070        // picker
071        // picker = new vtkPropPicker();
072        // picker.PickFromListOff();
073
074        picker = new vtkCellPicker();
075        picker.PickFromListOff();
076
077        // set ambient color to white
078        lgt.SetAmbientColor(1d, 1d, 1d);
079        lightFollowCamera = true;
080
081        // assign default renderer to layer 0 (should be the case by default)
082        ren.SetLayer(0);
083
084        // initialize axis
085        axisRenderer = new vtkRenderer();
086        // BUG: with OpenGL window the global render window viewport is limited to the last layer viewport dimension
087        // axisRenderer.SetViewport(0.0, 0.0, 0.2, 0.2);
088        axisRenderer.SetLayer(1);
089        axisRenderer.InteractiveOff();
090
091        rw.AddRenderer(axisRenderer);
092        rw.SetNumberOfLayers(2);
093
094        axisCam = axisRenderer.GetActiveCamera();
095
096        axis = new vtkAxesActor();
097        axisRenderer.AddActor(axis);
098
099        // default axis offset and scale
100        axisOffset = new int[] {124, 124};
101        axisScale = 1;
102
103        // reset camera
104        axisCam.SetViewUp(0, -1, 0);
105        axisCam.Elevation(210);
106        axisCam.SetParallelProjection(1);
107        axisRenderer.ResetCamera();
108        axisRenderer.ResetCameraClippingRange();
109
110        // used for restore quality rendering after a given amount of time
111        fineRenderingTime = 0;
112        renderingMonitor = new Thread(this, "VTK panel rendering monitor");
113        renderingMonitor.start();
114
115        addMouseListener(this);
116        addMouseMotionListener(this);
117        addMouseWheelListener(this);
118        addKeyListener(this);
119    }
120
121    @Override
122    protected void delete()
123    {
124        // stop thread
125        fineRenderingTime = 0;
126        renderingMonitor.interrupt();
127
128        super.delete();
129
130        lock.lock();
131        try
132        {
133            // release VTK objects
134            axisCam = null;
135            axis = null;
136            axisRenderer = null;
137            picker = null;
138
139            // call it once in parent as this can take a lot fo time
140            // vtkObjectBase.JAVA_OBJECT_MANAGER.gc(false);
141        }
142        finally
143        {
144            // removing the renderWindow is let to the superclass
145            // because in the very special case of an AWT component
146            // under Linux, destroying renderWindow crashes.
147            lock.unlock();
148        }
149    }
150
151    @Override
152    public void removeNotify()
153    {
154        // cancel fine rendering request
155        fineRenderingTime = 0;
156
157        super.removeNotify();
158    }
159
160    @Override
161    public void sizeChanged()
162    {
163        super.sizeChanged();
164
165        updateAxisView();
166    }
167
168    /**
169     * Return picker object.
170     */
171    public vtkPicker getPicker()
172    {
173        return picker;
174    }
175
176    /**
177     * Return the actor for axis orientation display.
178     */
179    public vtkAxesActor getAxesActor()
180    {
181        return axis;
182    }
183
184    public boolean getLightFollowCamera()
185    {
186        return lightFollowCamera;
187    }
188
189    /**
190     * Return true if the axis orientation display is enabled
191     */
192    public boolean isAxisOrientationDisplayEnable()
193    {
194        return (axis.GetVisibility() == 0) ? false : true;
195    }
196
197    /**
198     * Returns the offset from border ({X, Y} format) for the axis orientation display
199     */
200    public int[] getAxisOrientationDisplayOffset()
201    {
202        return axisOffset;
203    }
204
205    /**
206     * Returns the scale factor (default = 1) for the axis orientation display
207     */
208    public double getAxisOrientationDisplayScale()
209    {
210        return axisScale;
211    }
212
213    /**
214     * Set to <code>true</code> to automatically update light position to camera position when camera move.
215     */
216    public void setLightFollowCamera(boolean value)
217    {
218        lightFollowCamera = value;
219    }
220
221    /**
222     * Return true if the axis orientation display is enabled
223     */
224    public void setAxisOrientationDisplayEnable(boolean value)
225    {
226        axis.SetVisibility(value ? 1 : 0);
227        updateAxisView();
228    }
229
230    /**
231     * Sets the offset from border ({X, Y} format) for the axis orientation display (default = {130, 130})
232     */
233    public void setAxisOrientationDisplayOffset(int[] value)
234    {
235        axisOffset = value;
236        updateAxisView();
237    }
238
239    /**
240     * Returns the scale factor (default = 1) for the axis orientation display
241     */
242    public void setAxisOrientationDisplayScale(double value)
243    {
244        axisScale = value;
245        updateAxisView();
246    }
247
248    /**
249     * @deprecated Use {@link #pickActor(int, int)} instead
250     */
251    @Deprecated
252    public void pickActor(int x, int y)
253    {
254        pick(x, y);
255    }
256
257    /**
258     * Pick object at specified position and return it.
259     */
260    public vtkProp pick(int x, int y)
261    {
262        lock();
263        try
264        {
265            picker.Pick(x, rw.GetSize()[1] - y, 0, ren);
266        }
267        finally
268        {
269            unlock();
270        }
271
272        return picker.GetViewProp();
273    }
274
275    /**
276     * Translate specified camera view
277     */
278    public void translateView(vtkCamera c, vtkRenderer r, double dx, double dy)
279    {
280        // translation mode
281        double FPoint[];
282        double PPoint[];
283        double APoint[] = new double[3];
284        double RPoint[];
285        double focalDepth;
286
287        lock();
288        try
289        {
290            // get the current focal point and position
291            FPoint = c.GetFocalPoint();
292            PPoint = c.GetPosition();
293
294            // calculate the focal depth since we'll be using it a lot
295            r.SetWorldPoint(FPoint[0], FPoint[1], FPoint[2], 1.0);
296            r.WorldToDisplay();
297            focalDepth = r.GetDisplayPoint()[2];
298
299            final int[] size = rw.GetSize();
300            APoint[0] = (size[0] / 2.0) + dx;
301            APoint[1] = (size[1] / 2.0) + dy;
302            APoint[2] = focalDepth;
303            r.SetDisplayPoint(APoint);
304            r.DisplayToWorld();
305            RPoint = r.GetWorldPoint();
306            if (RPoint[3] != 0.0)
307            {
308                RPoint[0] = RPoint[0] / RPoint[3];
309                RPoint[1] = RPoint[1] / RPoint[3];
310                RPoint[2] = RPoint[2] / RPoint[3];
311            }
312
313            /*
314             * Compute a translation vector, moving everything 1/2 the distance
315             * to the cursor. (Arbitrary scale factor)
316             */
317            c.SetFocalPoint((FPoint[0] - RPoint[0]) / 2.0 + FPoint[0], (FPoint[1] - RPoint[1]) / 2.0 + FPoint[1],
318                    (FPoint[2] - RPoint[2]) / 2.0 + FPoint[2]);
319            c.SetPosition((FPoint[0] - RPoint[0]) / 2.0 + PPoint[0], (FPoint[1] - RPoint[1]) / 2.0 + PPoint[1],
320                    (FPoint[2] - RPoint[2]) / 2.0 + PPoint[2]);
321            r.ResetCameraClippingRange();
322        }
323        finally
324        {
325            unlock();
326        }
327    }
328
329    /**
330     * Rotate specified camera view
331     */
332    public void rotateView(vtkCamera c, vtkRenderer r, int dx, int dy)
333    {
334        lock();
335        try
336        {
337            // rotation mode
338            c.Azimuth(dx);
339            c.Elevation(dy);
340            c.OrthogonalizeViewUp();
341            r.ResetCameraClippingRange();
342        }
343        finally
344        {
345            unlock();
346        }
347    }
348
349    /**
350     * Zoom current view by specified factor (value < 1d means unzoom while value > 1d mean zoom)
351     */
352    public void zoomView(vtkCamera c, vtkRenderer r, double factor)
353    {
354        lock();
355        try
356        {
357            if (c.GetParallelProjection() == 1)
358                c.SetParallelScale(c.GetParallelScale() / factor);
359            else
360            {
361                c.Dolly(factor);
362                r.ResetCameraClippingRange();
363            }
364        }
365        finally
366        {
367            unlock();
368        }
369    }
370
371    /**
372     * Translate current camera view
373     */
374    public void translateView(double dx, double dy)
375    {
376        translateView(cam, ren, dx, dy);
377        // adjust light position
378        if (getLightFollowCamera())
379            setLightToCameraPosition(lgt, cam);
380    }
381
382    /**
383     * Rotate current camera view
384     */
385    public void rotateView(int dx, int dy)
386    {
387        // rotate world view
388        rotateView(cam, ren, dx, dy);
389        // adjust light position
390        if (getLightFollowCamera())
391            setLightToCameraPosition(lgt, cam);
392        // update axis camera
393        updateAxisView();
394    }
395
396    /**
397     * Zoom current view by specified factor (negative value means unzoom)
398     */
399    public void zoomView(double factor)
400    {
401        // zoom world
402        zoomView(cam, ren, factor);
403        // update axis camera
404        updateAxisView();
405    }
406
407    /**
408     * Set the specified light at the same position than the specified camera
409     */
410    public static void setLightToCameraPosition(vtkLight l, vtkCamera c)
411    {
412        l.SetPosition(c.GetPosition());
413        l.SetFocalPoint(c.GetFocalPoint());
414    }
415
416    /**
417     * Set coarse and fast rendering mode immediately
418     * 
419     * @see #setCoarseRendering(long)
420     * @see #setFineRendering()
421     */
422    public void setCoarseRendering()
423    {
424        // cancel pending fine rendering restoration
425        fineRenderingTime = 0;
426
427        if (rw.GetDesiredUpdateRate() == 20d)
428            return;
429
430        lock();
431        try
432        {
433            // set fast rendering
434            rw.SetDesiredUpdateRate(20d);
435        }
436        finally
437        {
438            unlock();
439        }
440    }
441
442    /**
443     * Set coarse and fast rendering mode <b>for the specified amount of time</b> (in ms).<br>
444     * Setting it to 0 means for always.
445     * 
446     * @see #setFineRendering(long)
447     */
448    public void setCoarseRendering(long time)
449    {
450        // want fast update
451        setCoarseRendering();
452
453        if (time > 0)
454            fineRenderingTime = System.currentTimeMillis() + time;
455    }
456
457    /**
458     * Set fine (and possibly slow) rendering mode immediately
459     * 
460     * @see #setFineRendering(long)
461     * @see #setCoarseRendering()
462     */
463    public void setFineRendering()
464    {
465        // cancel pending fine rendering restoration
466        fineRenderingTime = 0;
467
468        if (rw.GetDesiredUpdateRate() == 0.01)
469            return;
470
471        lock();
472        try
473        {
474            // set quality rendering
475            rw.SetDesiredUpdateRate(0.01);
476        }
477        finally
478        {
479            unlock();
480        }
481    }
482
483    /**
484     * Set fine (and possibly slow) rendering <b>after</b> specified time delay (in ms).<br>
485     * Using 0 means we want to immediately switch to fine rendering.
486     * 
487     * @see #setCoarseRendering(long)
488     */
489    public void setFineRendering(long delay)
490    {
491        if (delay > 0)
492            fineRenderingTime = System.currentTimeMillis() + delay;
493        else
494            // set back quality rendering immediately
495            setFineRendering();
496    }
497
498    /**
499     * Update axis display depending the current scene camera view.<br>
500     * You should call it after having modified camera settings.
501     */
502    public void updateAxisView()
503    {
504        if (!isWindowSet())
505            return;
506
507        lock();
508        try
509        {
510            double pos[] = cam.GetPosition();
511            double fp[] = cam.GetFocalPoint();
512            double viewup[] = cam.GetViewUp();
513
514            // mimic axis camera position to scene camera position
515            axisCam.SetPosition(pos);
516            axisCam.SetFocalPoint(fp);
517            axisCam.SetViewUp(viewup);
518            axisRenderer.ResetCamera();
519
520            final int[] size = rw.GetSize();
521            // adjust scale
522            final double scale = size[1] / 512d;
523            // adjust offset
524            final int w = (int) (size[0] - (axisOffset[0] * scale));
525            final int h = (int) (size[1] - (axisOffset[1] * scale));
526            // zoom and translate
527            zoomView(axisCam, axisRenderer, axisScale * (axisCam.GetDistance() / 17d));
528            translateView(axisCam, axisRenderer, -w, -h);
529        }
530        finally
531        {
532            unlock();
533        }
534    }
535
536    @Override
537    public void run()
538    {
539        while (!Thread.currentThread().isInterrupted())
540        {
541            // nothing to do
542            if (fineRenderingTime == 0)
543                ThreadUtil.sleep(1);
544            else
545            {
546                // thread used for restoring fine rendering after a certain amount of time
547                if (System.currentTimeMillis() >= fineRenderingTime)
548                {
549                    // set back quality rendering
550                    setFineRendering();
551                    // request repaint
552                    repaint();
553                    // done
554                    fineRenderingTime = 0;
555                }
556                // wait until delay elapsed
557                else
558                    ThreadUtil.sleep(1);
559            }
560        }
561    }
562
563    @Override
564    public void mouseEntered(MouseEvent e)
565    {
566        // nothing to do here
567    }
568
569    @Override
570    public void mouseExited(MouseEvent e)
571    {
572        // nothing to do here
573    }
574
575    @Override
576    public void mouseClicked(MouseEvent e)
577    {
578        if (e.isConsumed())
579            return;
580
581        // nothing to do here
582    }
583
584    @Override
585    public void mousePressed(MouseEvent e)
586    {
587        if (e.isConsumed())
588            return;
589
590        // nothing to do here
591    }
592
593    @Override
594    public void mouseReleased(MouseEvent e)
595    {
596        if (e.isConsumed())
597            return;
598
599        // nothing to do here
600    }
601
602    @Override
603    public void mouseMoved(MouseEvent e)
604    {
605        // just save mouse position
606        lastX = e.getX();
607        lastY = e.getY();
608    }
609
610    @Override
611    public void mouseDragged(MouseEvent e)
612    {
613        // camera not yet defined --> exit
614        if (cam == null)
615            return;
616
617        if (e.isConsumed())
618            return;
619        if (ren.VisibleActorCount() == 0)
620            return;
621
622        // consume event
623        e.consume();
624
625        // want fast update
626        setCoarseRendering();
627        // abort current rendering
628        rw.SetAbortRender(1);
629
630        // get current mouse position
631        final int x = e.getX();
632        final int y = e.getY();
633        int deltaX = (lastX - x);
634        int deltaY = (lastY - y);
635
636        // faster movement with control modifier
637        if (EventUtil.isControlDown(e))
638        {
639            deltaX *= 3;
640            deltaY *= 3;
641        }
642
643        if (EventUtil.isRightMouseButton(e) || (EventUtil.isLeftMouseButton(e) && EventUtil.isShiftDown(e)))
644            // translation mode
645            translateView(-deltaX * 2, deltaY * 2);
646        else if (EventUtil.isMiddleMouseButton(e))
647            // zoom mode
648            zoomView(Math.pow(1.02, -deltaY));
649        else
650            // rotation mode
651            rotateView(deltaX, -deltaY);
652
653        // save mouse position
654        lastX = x;
655        lastY = y;
656
657        // request repaint
658        repaint();
659
660        // restore quality rendering in 1 second
661        setFineRendering(1000);
662    }
663
664    @Override
665    public void mouseWheelMoved(MouseWheelEvent e)
666    {
667        // camera not yet defined --> exit
668        if (cam == null)
669            return;
670
671        if (e.isConsumed())
672            return;
673        if (ren.VisibleActorCount() == 0)
674            return;
675
676        // consume event
677        e.consume();
678
679        // want fast update
680        setCoarseRendering();
681        // abort current rendering
682        rw.SetAbortRender(1);
683
684        // get delta
685        double delta = e.getWheelRotation() * CanvasPreferences.getMouseWheelSensitivity();
686        if (CanvasPreferences.getInvertMouseWheelAxis())
687            delta = -delta;
688
689        // faster movement with control modifier
690        if (EventUtil.isControlDown(e))
691            delta *= 3d;
692
693        zoomView(Math.pow(1.02, delta));
694
695        // request repaint
696        repaint();
697
698        // restore quality rendering in 1 second
699        setFineRendering(1000);
700    }
701
702    @Override
703    public void keyTyped(KeyEvent e)
704    {
705        //
706    }
707
708    @Override
709    public void keyPressed(KeyEvent e)
710    {
711        if (e.isConsumed())
712            return;
713        if (ren.VisibleActorCount() == 0)
714            return;
715
716        vtkActorCollection ac;
717        vtkActor anActor;
718        int i;
719
720        switch (e.getKeyChar())
721        {
722            case 'r': // reset camera
723                resetCamera();
724                repaint();
725                // consume event
726                e.consume();
727                break;
728
729            case 'w': // wireframe mode
730                lock();
731                try
732                {
733                    ac = ren.GetActors();
734                    ac.InitTraversal();
735                    for (i = 0; i < ac.GetNumberOfItems(); i++)
736                    {
737                        anActor = ac.GetNextActor();
738                        anActor.GetProperty().SetRepresentationToWireframe();
739                    }
740                }
741                finally
742                {
743                    unlock();
744                }
745                repaint();
746                // consume event
747                e.consume();
748                break;
749
750            case 's':
751                lock();
752                try
753                {
754                    ac = ren.GetActors();
755                    ac.InitTraversal();
756                    for (i = 0; i < ac.GetNumberOfItems(); i++)
757                    {
758                        anActor = ac.GetNextActor();
759                        anActor.GetProperty().SetRepresentationToSurface();
760                    }
761                }
762                finally
763                {
764                    unlock();
765                }
766                repaint();
767                // consume event
768                e.consume();
769                break;
770        }
771    }
772
773    @Override
774    public void keyReleased(KeyEvent e)
775    {
776        if (e.isConsumed())
777            return;
778    }
779}