/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.vtk;
import icy.preferences.CanvasPreferences;
import icy.system.thread.ThreadUtil;
import icy.util.EventUtil;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import vtk.vtkActor;
import vtk.vtkActorCollection;
import vtk.vtkAxesActor;
import vtk.vtkCamera;
import vtk.vtkCellPicker;
import vtk.vtkLight;
import vtk.vtkPicker;
import vtk.vtkProp;
import vtk.vtkRenderer;
/**
* Icy custom VTK panel used for VTK rendering.
*
* @author stephane dallongeville
*/
public class IcyVtkPanel extends VtkJoglPanel implements MouseListener, MouseMotionListener, MouseWheelListener,
KeyListener, Runnable
{
/**
*
*/
private static final long serialVersionUID = -8455671369400627703L;
protected Thread renderingMonitor;
// protected vtkPropPicker picker;
protected vtkCellPicker picker;
protected vtkAxesActor axis;
protected vtkRenderer axisRenderer;
protected vtkCamera axisCam;
protected int axisOffset[];
protected double axisScale;
protected boolean lightFollowCamera;
protected volatile long fineRenderingTime;
public IcyVtkPanel()
{
super();
// picker
// picker = new vtkPropPicker();
// picker.PickFromListOff();
picker = new vtkCellPicker();
picker.PickFromListOff();
// set ambient color to white
lgt.SetAmbientColor(1d, 1d, 1d);
lightFollowCamera = true;
// assign default renderer to layer 0 (should be the case by default)
ren.SetLayer(0);
// initialize axis
axisRenderer = new vtkRenderer();
// BUG: with OpenGL window the global render window viewport is limited to the last layer viewport dimension
// axisRenderer.SetViewport(0.0, 0.0, 0.2, 0.2);
axisRenderer.SetLayer(1);
axisRenderer.InteractiveOff();
rw.AddRenderer(axisRenderer);
rw.SetNumberOfLayers(2);
axisCam = axisRenderer.GetActiveCamera();
axis = new vtkAxesActor();
axisRenderer.AddActor(axis);
// default axis offset and scale
axisOffset = new int[] {124, 124};
axisScale = 1;
// reset camera
axisCam.SetViewUp(0, -1, 0);
axisCam.Elevation(210);
axisCam.SetParallelProjection(1);
axisRenderer.ResetCamera();
axisRenderer.ResetCameraClippingRange();
// used for restore quality rendering after a given amount of time
fineRenderingTime = 0;
renderingMonitor = new Thread(this, "VTK panel rendering monitor");
renderingMonitor.start();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
addKeyListener(this);
}
@Override
protected void delete()
{
// stop thread
fineRenderingTime = 0;
renderingMonitor.interrupt();
super.delete();
lock.lock();
try
{
// release VTK objects
axisCam = null;
axis = null;
axisRenderer = null;
picker = null;
// call it once in parent as this can take a lot fo time
// vtkObjectBase.JAVA_OBJECT_MANAGER.gc(false);
}
finally
{
// removing the renderWindow is let to the superclass
// because in the very special case of an AWT component
// under Linux, destroying renderWindow crashes.
lock.unlock();
}
}
@Override
public void removeNotify()
{
// cancel fine rendering request
fineRenderingTime = 0;
super.removeNotify();
}
@Override
public void sizeChanged()
{
super.sizeChanged();
updateAxisView();
}
/**
* Return picker object.
*/
public vtkPicker getPicker()
{
return picker;
}
/**
* Return the actor for axis orientation display.
*/
public vtkAxesActor getAxesActor()
{
return axis;
}
public boolean getLightFollowCamera()
{
return lightFollowCamera;
}
/**
* Return true if the axis orientation display is enabled
*/
public boolean isAxisOrientationDisplayEnable()
{
return (axis.GetVisibility() == 0) ? false : true;
}
/**
* Returns the offset from border ({X, Y} format) for the axis orientation display
*/
public int[] getAxisOrientationDisplayOffset()
{
return axisOffset;
}
/**
* Returns the scale factor (default = 1) for the axis orientation display
*/
public double getAxisOrientationDisplayScale()
{
return axisScale;
}
/**
* Set to <code>true</code> to automatically update light position to camera position when camera move.
*/
public void setLightFollowCamera(boolean value)
{
lightFollowCamera = value;
}
/**
* Return true if the axis orientation display is enabled
*/
public void setAxisOrientationDisplayEnable(boolean value)
{
axis.SetVisibility(value ? 1 : 0);
updateAxisView();
}
/**
* Sets the offset from border ({X, Y} format) for the axis orientation display (default = {130, 130})
*/
public void setAxisOrientationDisplayOffset(int[] value)
{
axisOffset = value;
updateAxisView();
}
/**
* Returns the scale factor (default = 1) for the axis orientation display
*/
public void setAxisOrientationDisplayScale(double value)
{
axisScale = value;
updateAxisView();
}
/**
* @deprecated Use {@link #pickActor(int, int)} instead
*/
@Deprecated
public void pickActor(int x, int y)
{
pick(x, y);
}
/**
* Pick object at specified position and return it.
*/
public vtkProp pick(int x, int y)
{
lock();
try
{
picker.Pick(x, rw.GetSize()[1] - y, 0, ren);
}
finally
{
unlock();
}
return picker.GetViewProp();
}
/**
* Translate specified camera view
*/
public void translateView(vtkCamera c, vtkRenderer r, double dx, double dy)
{
// translation mode
double FPoint[];
double PPoint[];
double APoint[] = new double[3];
double RPoint[];
double focalDepth;
lock();
try
{
// get the current focal point and position
FPoint = c.GetFocalPoint();
PPoint = c.GetPosition();
// calculate the focal depth since we'll be using it a lot
r.SetWorldPoint(FPoint[0], FPoint[1], FPoint[2], 1.0);
r.WorldToDisplay();
focalDepth = r.GetDisplayPoint()[2];
final int[] size = rw.GetSize();
APoint[0] = (size[0] / 2.0) + dx;
APoint[1] = (size[1] / 2.0) + dy;
APoint[2] = focalDepth;
r.SetDisplayPoint(APoint);
r.DisplayToWorld();
RPoint = r.GetWorldPoint();
if (RPoint[3] != 0.0)
{
RPoint[0] = RPoint[0] / RPoint[3];
RPoint[1] = RPoint[1] / RPoint[3];
RPoint[2] = RPoint[2] / RPoint[3];
}
/*
* Compute a translation vector, moving everything 1/2 the distance
* to the cursor. (Arbitrary scale factor)
*/
c.SetFocalPoint((FPoint[0] - RPoint[0]) / 2.0 + FPoint[0], (FPoint[1] - RPoint[1]) / 2.0 + FPoint[1],
(FPoint[2] - RPoint[2]) / 2.0 + FPoint[2]);
c.SetPosition((FPoint[0] - RPoint[0]) / 2.0 + PPoint[0], (FPoint[1] - RPoint[1]) / 2.0 + PPoint[1],
(FPoint[2] - RPoint[2]) / 2.0 + PPoint[2]);
r.ResetCameraClippingRange();
}
finally
{
unlock();
}
}
/**
* Rotate specified camera view
*/
public void rotateView(vtkCamera c, vtkRenderer r, int dx, int dy)
{
lock();
try
{
// rotation mode
c.Azimuth(dx);
c.Elevation(dy);
c.OrthogonalizeViewUp();
r.ResetCameraClippingRange();
}
finally
{
unlock();
}
}
/**
* Zoom current view by specified factor (value < 1d means unzoom while value > 1d mean zoom)
*/
public void zoomView(vtkCamera c, vtkRenderer r, double factor)
{
lock();
try
{
if (c.GetParallelProjection() == 1)
c.SetParallelScale(c.GetParallelScale() / factor);
else
{
c.Dolly(factor);
r.ResetCameraClippingRange();
}
}
finally
{
unlock();
}
}
/**
* Translate current camera view
*/
public void translateView(double dx, double dy)
{
translateView(cam, ren, dx, dy);
// adjust light position
if (getLightFollowCamera())
setLightToCameraPosition(lgt, cam);
}
/**
* Rotate current camera view
*/
public void rotateView(int dx, int dy)
{
// rotate world view
rotateView(cam, ren, dx, dy);
// adjust light position
if (getLightFollowCamera())
setLightToCameraPosition(lgt, cam);
// update axis camera
updateAxisView();
}
/**
* Zoom current view by specified factor (negative value means unzoom)
*/
public void zoomView(double factor)
{
// zoom world
zoomView(cam, ren, factor);
// update axis camera
updateAxisView();
}
/**
* Set the specified light at the same position than the specified camera
*/
public static void setLightToCameraPosition(vtkLight l, vtkCamera c)
{
l.SetPosition(c.GetPosition());
l.SetFocalPoint(c.GetFocalPoint());
}
/**
* Set coarse and fast rendering mode immediately
*
* @see #setCoarseRendering(long)
* @see #setFineRendering()
*/
public void setCoarseRendering()
{
// cancel pending fine rendering restoration
fineRenderingTime = 0;
if (rw.GetDesiredUpdateRate() == 20d)
return;
lock();
try
{
// set fast rendering
rw.SetDesiredUpdateRate(20d);
}
finally
{
unlock();
}
}
/**
* Set coarse and fast rendering mode <b>for the specified amount of time</b> (in ms).<br>
* Setting it to 0 means for always.
*
* @see #setFineRendering(long)
*/
public void setCoarseRendering(long time)
{
// want fast update
setCoarseRendering();
if (time > 0)
fineRenderingTime = System.currentTimeMillis() + time;
}
/**
* Set fine (and possibly slow) rendering mode immediately
*
* @see #setFineRendering(long)
* @see #setCoarseRendering()
*/
public void setFineRendering()
{
// cancel pending fine rendering restoration
fineRenderingTime = 0;
if (rw.GetDesiredUpdateRate() == 0.01)
return;
lock();
try
{
// set quality rendering
rw.SetDesiredUpdateRate(0.01);
}
finally
{
unlock();
}
}
/**
* Set fine (and possibly slow) rendering <b>after</b> specified time delay (in ms).<br>
* Using 0 means we want to immediately switch to fine rendering.
*
* @see #setCoarseRendering(long)
*/
public void setFineRendering(long delay)
{
if (delay > 0)
fineRenderingTime = System.currentTimeMillis() + delay;
else
// set back quality rendering immediately
setFineRendering();
}
/**
* Update axis display depending the current scene camera view.<br>
* You should call it after having modified camera settings.
*/
public void updateAxisView()
{
if (!isWindowSet())
return;
lock();
try
{
double pos[] = cam.GetPosition();
double fp[] = cam.GetFocalPoint();
double viewup[] = cam.GetViewUp();
// mimic axis camera position to scene camera position
axisCam.SetPosition(pos);
axisCam.SetFocalPoint(fp);
axisCam.SetViewUp(viewup);
axisRenderer.ResetCamera();
final int[] size = rw.GetSize();
// adjust scale
final double scale = size[1] / 512d;
// adjust offset
final int w = (int) (size[0] - (axisOffset[0] * scale));
final int h = (int) (size[1] - (axisOffset[1] * scale));
// zoom and translate
zoomView(axisCam, axisRenderer, axisScale * (axisCam.GetDistance() / 17d));
translateView(axisCam, axisRenderer, -w, -h);
}
finally
{
unlock();
}
}
@Override
public void run()
{
while (!Thread.currentThread().isInterrupted())
{
// nothing to do
if (fineRenderingTime == 0)
ThreadUtil.sleep(1);
else
{
// thread used for restoring fine rendering after a certain amount of time
if (System.currentTimeMillis() >= fineRenderingTime)
{
// set back quality rendering
setFineRendering();
// request repaint
repaint();
// done
fineRenderingTime = 0;
}
// wait until delay elapsed
else
ThreadUtil.sleep(1);
}
}
}
@Override
public void lock()
{
// if (!isWindowSet())
// return;
super.lock();
}
@Override
public void unlock()
{
// if (!isWindowSet())
// return;
super.unlock();
}
@Override
public void mouseEntered(MouseEvent e)
{
// nothing to do here
}
@Override
public void mouseExited(MouseEvent e)
{
// nothing to do here
}
@Override
public void mouseClicked(MouseEvent e)
{
if (e.isConsumed())
return;
// nothing to do here
}
@Override
public void mousePressed(MouseEvent e)
{
if (e.isConsumed())
return;
// nothing to do here
}
@Override
public void mouseReleased(MouseEvent e)
{
if (e.isConsumed())
return;
// nothing to do here
}
@Override
public void mouseMoved(MouseEvent e)
{
// just save mouse position
lastX = e.getX();
lastY = e.getY();
}
@Override
public void mouseDragged(MouseEvent e)
{
// camera not yet defined --> exit
if (cam == null)
return;
if (e.isConsumed())
return;
if (ren.VisibleActorCount() == 0)
return;
// consume event
e.consume();
// want fast update
setCoarseRendering();
// abort current rendering
rw.SetAbortRender(1);
// get current mouse position
final int x = e.getX();
final int y = e.getY();
int deltaX = (lastX - x);
int deltaY = (lastY - y);
// faster movement with control modifier
if (EventUtil.isControlDown(e))
{
deltaX *= 3;
deltaY *= 3;
}
if (EventUtil.isRightMouseButton(e) || (EventUtil.isLeftMouseButton(e) && EventUtil.isShiftDown(e)))
// translation mode
translateView(-deltaX * 2, deltaY * 2);
else if (EventUtil.isMiddleMouseButton(e))
// zoom mode
zoomView(Math.pow(1.02, -deltaY));
else
// rotation mode
rotateView(deltaX, -deltaY);
// save mouse position
lastX = x;
lastY = y;
// request repaint
repaint();
// restore quality rendering in 1 second
setFineRendering(1000);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
// camera not yet defined --> exit
if (cam == null)
return;
if (e.isConsumed())
return;
if (ren.VisibleActorCount() == 0)
return;
// consume event
e.consume();
// want fast update
setCoarseRendering();
// abort current rendering
rw.SetAbortRender(1);
// get delta
double delta = e.getWheelRotation() * CanvasPreferences.getMouseWheelSensitivity();
if (CanvasPreferences.getInvertMouseWheelAxis())
delta = -delta;
// faster movement with control modifier
if (EventUtil.isControlDown(e))
delta *= 3d;
zoomView(Math.pow(1.02, delta));
// request repaint
repaint();
// restore quality rendering in 1 second
setFineRendering(1000);
}
@Override
public void keyTyped(KeyEvent e)
{
//
}
@Override
public void keyPressed(KeyEvent e)
{
if (e.isConsumed())
return;
if (ren.VisibleActorCount() == 0)
return;
vtkActorCollection ac;
vtkActor anActor;
int i;
switch (e.getKeyChar())
{
case 'r': // reset camera
resetCamera();
repaint();
// consume event
e.consume();
break;
case 'w': // wireframe mode
lock();
try
{
ac = ren.GetActors();
ac.InitTraversal();
for (i = 0; i < ac.GetNumberOfItems(); i++)
{
anActor = ac.GetNextActor();
anActor.GetProperty().SetRepresentationToWireframe();
}
}
finally
{
unlock();
}
repaint();
// consume event
e.consume();
break;
case 's':
lock();
try
{
ac = ren.GetActors();
ac.InitTraversal();
for (i = 0; i < ac.GetNumberOfItems(); i++)
{
anActor = ac.GetNextActor();
anActor.GetProperty().SetRepresentationToSurface();
}
}
finally
{
unlock();
}
repaint();
// consume event
e.consume();
break;
}
}
@Override
public void keyReleased(KeyEvent e)
{
if (e.isConsumed())
return;
}
}