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}