CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxCameraStyleForView.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxCameraStyleForView.h"
13 
14 #include <vtkRenderer.h>
15 #include <vtkCamera.h>
16 #include <vtkLightCollection.h>
17 #include <vtkLight.h>
18 
19 #include "cxTrackingService.h"
20 #include "cxToolRep3D.h"
21 #include "cxView.h"
22 #include "boost/bind.hpp"
23 #include <vtkRenderWindow.h>
24 #include "vtkInteractorStyleUnicam.h"
25 #include "vtkInteractorStyleTrackballCamera.h"
26 #include "cxCoreServices.h"
27 #include "cxViewportListener.h"
28 
29 #include "cxTool.h"
30 #include <vtkRenderWindowInteractor.h>
31 #include "cxPatientModelService.h"
32 #include "cxRepContainer.h"
33 #include "cxLogger.h"
35 #include "cxNavigationAlgorithms.h"
36 #include "cxDoubleRange.h"
37 
38 namespace cx
39 {
40 
42 {
43  CX_LOG_CHANNEL_DEBUG("CA") << " info.focus="<<info.focus;
44  CX_LOG_CHANNEL_DEBUG("CA") << " info.pos="<<info.pos;
45  CX_LOG_CHANNEL_DEBUG("CA") << " info.vup="<<info.vup;
46  CX_LOG_CHANNEL_DEBUG("CA") << " info.vpn() ="<<info.vpn();
47  CX_LOG_CHANNEL_DEBUG("CA") << " info.distance() ="<<info.distance();
48 }
49 
50 
51 
53  mBlockCameraUpdate(false),
54  mBackend(backend)
55 {
56  mViewportListener.reset(new ViewportListener);
57  mViewportListener->setCallback(boost::bind(&CameraStyleForView::viewportChangedSlot, this));
58 
59  mPreRenderListener.reset(new ViewportPreRenderListener);
60  mPreRenderListener->setCallback(boost::bind(&CameraStyleForView::onPreRender, this));
61 
62  connect(mBackend->tracking().get(), &TrackingService::activeToolChanged,
63  this, &CameraStyleForView::activeToolChangedSlot);
64 }
65 
67 {
68  mViewportListener->stopListen();
69  mPreRenderListener->stopListen();
70 
71  this->disconnectTool();
72  mView = widget;
73  this->connectTool();
74 
75  mViewportListener->startListen(this->getRenderer());
76  mPreRenderListener->startListen(this->getRenderer());
77 }
78 
79 
80 ViewPtr CameraStyleForView::getView() const
81 {
82  return mView;
83 }
84 
85 void CameraStyleForView::viewportChangedSlot()
86 {
87  if (mBlockCameraUpdate)
88  return;
89  this->setModified();
90 }
91 
92 void CameraStyleForView::onPreRender()
93 {
94  this->applyCameraStyle();
95 }
96 
97 ToolRep3DPtr CameraStyleForView::getToolRep() const
98 {
99  if (!this->getView())
100  return ToolRep3DPtr();
101 
102  ToolRep3DPtr rep = RepContainer(this->getView()->getReps()).findFirst<ToolRep3D>(mFollowingTool);
103  return rep;
104 }
105 
106 vtkRendererPtr CameraStyleForView::getRenderer() const
107 {
108  if (!this->getView())
109  return vtkRendererPtr();
110  return this->getView()->getRenderer();
111 }
112 
113 vtkCameraPtr CameraStyleForView::getCamera() const
114 {
115  if (!this->getRenderer())
116  return vtkCameraPtr();
117  return this->getRenderer()->GetActiveCamera();
118 }
119 
120 void CameraStyleForView::setModified()
121 {
122  mPreRenderListener->setModified();
123 }
124 
125 void CameraStyleForView::applyCameraStyle()
126 {
127  vtkCameraPtr camera = this->getCamera();
128  if (!camera)
129  return;
130 
131  CameraInfo cam_old(camera);
132  CameraInfo cam_new = cam_old;
133 
134  cam_new.viewAngle = mStyle.mCameraViewAngle/M_PI*180;
135 
136  if (!mStyle.mFocusROI.isEmpty())
137  {
138  DoubleBoundingBox3D roi_r = this->getROI(mStyle.mFocusROI).getBox();
139  if (roi_r != DoubleBoundingBox3D::zero())
140  cam_new.focus = roi_r.center();
141  }
142 
143  if (mFollowingTool)
144  {
145  Transform3D rMto = this->get_rMto();
146 
147  // view up is relative to tool
148  cam_new.vup = rMto.vector(Vector3D(-1, 0, 0));
149 
150  if (mStyle.mFocusFollowTool)
151  {
152  // set focus to tool offset point
153  cam_new.focus = rMto.coord(Vector3D::Zero());
154  }
155 
156  if (mStyle.mCameraFollowTool)
157  {
158  // set camera on the tool line, keeping the previous distance from the focus.
159  Vector3D tooloffset = rMto.coord(Vector3D::Zero());
160  Vector3D e_tool = rMto.vector(Vector3D(0, 0, 1));
162  e_tool,
163  cam_old.distance(),
164  cam_new.focus);
165  }
166  }
167 
168  if (mStyle.mTableLock)
169  {
170  Vector3D table_up = mBackend->patient()->getOperatingTable().getVectorUp();
171  cam_new.vup = table_up;
172  }
173 
174  // reset vup based on vpn (do not change vpn after this point)
175  if (!similar(cam_new.vup, cam_old.vup))
176  {
177  cam_new.vup = NavigationAlgorithms::orthogonalize_vup(cam_new.vup, cam_new.vpn(), cam_old.vup);
178  }
179 
180  if (!mStyle.mAutoZoomROI.isEmpty())
181  {
182  cam_new = this->viewEntireAutoZoomROI(cam_new);
183  }
184 
185  if (mStyle.mCameraFollowTool)
186  {
187  cam_new.pos = NavigationAlgorithms::elevateCamera(mStyle.mElevation, cam_new.pos, cam_new.focus, cam_new.vup);
188  }
189 
190  this->updateCamera(cam_new);
191 }
192 
193 Transform3D CameraStyleForView::get_rMto()
194 {
195  Transform3D rMpr = mBackend->patient()->get_rMpr();
196  Transform3D prMt = mFollowingTool->get_prMt();
197  double offset = mFollowingTool->getTooltipOffset();
198  Transform3D tMto = createTransformTranslate(Vector3D(0, 0, offset));
199  Transform3D rMto = rMpr * prMt * tMto;
200  return rMto;
201 }
202 
203 Vector3D CameraStyleForView::getToolTip_r()
204 {
205  Transform3D rMto = this->get_rMto();
206  return rMto.coord(Vector3D::Zero());
207 }
208 
209 
210 CameraInfo CameraStyleForView::viewEntireAutoZoomROI(CameraInfo info)
211 {
212  CameraInfo retval = info;
213  if (mStyle.mAutoZoomROI.isEmpty())
214  return retval;
215 
216  RegionOfInterest roi_r = this->getROI(mStyle.mAutoZoomROI);
217  if (!roi_r.isValid())
218  return retval;
219 
220  double viewAngle = info.viewAngle/180.0*M_PI;
221 
222  this->getRenderer()->ComputeAspect();
223  double aspect[2];
224  this->getRenderer()->GetAspect(aspect);
225 
226  double viewAngle_vertical = viewAngle;
227  double viewAngle_horizontal = viewAngle*aspect[0];
228 
229  // move all calculations into a space p: (x,y,c)=(left,vup,focus)
230  // used to define a ROI bounding box aligned to the viewport.
231  Vector3D left = cross(info.vup, info.vpn());
232  Transform3D pMr = createTransformIJC(left, info.vup, info.focus).inv();
233  CameraInfo cam_proj;
234  cam_proj.focus = pMr.coord(info.focus);
235  cam_proj.vup = pMr.vector(info.vup);
236  cam_proj.pos = pMr.coord(info.pos);
237 
238  DoubleBoundingBox3D proj_bb = roi_r.getBox(pMr);
239 
240  Vector3D viewdirection = (cam_proj.focus - cam_proj.pos).normal();
241  cam_proj.pos = NavigationAlgorithms::findCameraPosByZoomingToROI(viewAngle_vertical,
242  viewAngle_horizontal,
243  cam_proj.focus,
244  cam_proj.vup,
245  cam_proj.vpn(),
246  proj_bb);
247  // keep focus at a const distance in front of the camera (if we dont do this, vpn might swap)
248  cam_proj.focus = cam_proj.pos + viewdirection * 100;
249 
250  cam_proj.pos = this->smoothZoomedCameraPosition(cam_proj.pos);
251 
252  retval.pos = pMr.inv().coord(cam_proj.pos);
253  retval.focus = pMr.inv().coord(cam_proj.focus);
254 
255  // experimental:
256  // IF inside bb: move pos to tool tip
257  // IF in front of bb: do nothing.
258  // IF in bbx2: interpolate between the two
259  // IF behind bb: keep pos inside bb
260  if (mStyle.mCameraLockToTooltip && mFollowingTool)
261  {
262  Transform3D rMto = this->get_rMto();
263  Vector3D proj_tool = (pMr*rMto).coord(Vector3D(0,0,0));
264  Vector3D e_z(0,0,1);
265  double bb_extension = 50; // distance from bb where we want to use only on-tool
266  double bb_extension_interpolate_interval = 50; // distance from bb where we want to interpolate between on-tool and off-tool
267 
268  bb_extension = 0; // distance from bb where we want to use only on-tool
269  bb_extension_interpolate_interval = 50; // distance from bb where we want to interpolate between on-tool and off-tool
270 
271  double tool_z = dot(proj_tool, e_z);
272  double bb_max_z = proj_bb[5];
273  double bb_ext_z = proj_bb[5] + bb_extension;
274 
275  RegionOfInterest notbehind_r = this->getROI(mStyle.mCameraNotBehindROI);
276  DoubleBoundingBox3D notbehind_proj = notbehind_r.getBox((pMr));
277  double notbehind = notbehind_proj[4];
278 
279  Vector3D new_pos;
280 
281  if (notbehind_r.isValid() && (tool_z < notbehind))
282  {
283  // behind roi: lock camera pos to closest pos inside bb
284  new_pos = proj_tool + (notbehind-tool_z)*e_z;
285 // CX_LOG_CHANNEL_DEBUG("CA") << " ** behind roi";
286  }
287  else
288  {
289  double s = (tool_z-bb_extension-bb_max_z)/bb_extension_interpolate_interval;
290  s = std::min(1.0, s);
291  s = std::max(0.0, s);
292  new_pos = (1.0-s)*proj_tool + (s)*cam_proj.pos;
293 // CX_LOG_CHANNEL_DEBUG("CA") << " ** roi pos= s=" << s;
294  }
295 
296  new_pos -= mStyle.mCameraTooltipOffset*e_z;
297 
298  // Move the camera onto the tool tip, keeping the distance vector constant.
299  // This gives the effect of _sitting on the tool tip_ while moving.
300  // Alternative: Dont change focal point, change view angle instead.
301  Vector3D delta = pMr.inv().coord(new_pos) - retval.pos;
302  retval.pos += delta;
303  retval.focus += delta;
304  }
305 
306  return retval;
307 }
308 
309 void CameraStyleForView::handleLights()
310 {
311  vtkRendererPtr renderer = this->getRenderer();
312 // CX_LOG_CHANNEL_DEBUG("CA") << "#lights: " << renderer->GetLights()->GetNumberOfItems();
313  renderer->GetLights()->InitTraversal();
314  vtkLight* light = renderer->GetLights()->GetNextItem();
315 // CX_LOG_CHANNEL_DEBUG("CA") << "light ";
316 // light->PrintSelf(std::cout, vtkIndent(2));
317 
318  // experiment: set a light to the left of the camera, pointing at focus
319  light->SetConeAngle(160);
320  light->SetLightTypeToCameraLight();
321  light->SetPosition(-0.5,0,1);
322 }
323 
328 Vector3D CameraStyleForView::smoothZoomedCameraPosition(Vector3D pos)
329 {
330  Vector3D filteredPos = pos;
331  filteredPos[2] = mZoomJitterFilter.newValue(pos[2]);
332  return filteredPos;
333 }
334 
335 RegionOfInterest CameraStyleForView::getROI(QString uid) const
336 {
337  DataPtr data = mBackend->patient()->getData(uid);
338  RegionOfInterestMetricPtr roi = boost::dynamic_pointer_cast<RegionOfInterestMetric>(data);
339  if (roi)
340  return roi->getROI();
341  return RegionOfInterest();
342 }
343 
344 void CameraStyleForView::activeToolChangedSlot()
345 {
346  ToolPtr newTool = mBackend->tracking()->getActiveTool();
347  if (newTool == mFollowingTool)
348  return;
349 
350  this->disconnectTool();
351  this->connectTool();
352 }
353 
354 bool CameraStyleForView::isToolFollowingStyle() const
355 {
356  return (mStyle.mCameraFollowTool || mStyle.mFocusFollowTool);
357 }
358 
359 void CameraStyleForView::connectTool()
360 {
361  if (!this->isToolFollowingStyle())
362  return;
363 
364  mFollowingTool = mBackend->tracking()->getActiveTool();
365 
366  if (!mFollowingTool)
367  return;
368 
369  if (!this->getView())
370  return;
371 
372  connect(mFollowingTool.get(), SIGNAL(toolTransformAndTimestamp(Transform3D, double)), this,
373  SLOT(setModified()));
374 
375  ToolRep3DPtr rep = this->getToolRep();
376  if (rep)
377  {
378  rep->setOffsetPointVisibleAtZeroOffset(false);
379  if (mStyle.mCameraFollowTool && fabs(mStyle.mElevation) < 0.01)
380  rep->setStayHiddenAfterVisible(true);
381  }
382 
383  this->setModified();
384 
385  report("Camera is following " + mFollowingTool->getName());
386 }
387 
388 void CameraStyleForView::disconnectTool()
389 {
390  if (mFollowingTool)
391  {
392  disconnect(mFollowingTool.get(), SIGNAL(toolTransformAndTimestamp(Transform3D, double)), this,
393  SLOT(setModified()));
394 
395  ToolRep3DPtr rep = this->getToolRep();
396  if (rep)
397  {
398  rep->setOffsetPointVisibleAtZeroOffset(true);
399  rep->setStayHiddenAfterVisible(false);
400  }
401 
402  mFollowingTool.reset();
403  }
404 }
405 
407 {
408  if (mStyle == style)
409  return;
410 
411  this->disconnectTool();
412 
413  if (style.mUniCam)
414  this->setInteractor(vtkInteractorStyleUnicamPtr::New());
415  else
416  this->setInteractor(vtkInteractorStyleTrackballCameraPtr::New());
417 
418  mStyle = style;
419 
420  this->connectTool();
421 }
422 
423 void CameraStyleForView::setInteractor(vtkSmartPointer<vtkInteractorStyle> style)
424 {
425  ViewPtr view = this->getView();
426  if (!view)
427  return;
428  vtkRenderWindowInteractor* interactor = view->getRenderWindow()->GetInteractor();
429  interactor->SetInteractorStyle(style);
430 }
431 
433 {
434  return mStyle;
435 }
436 
437 void CameraStyleForView::updateCamera(CameraInfo info)
438 {
439  vtkCameraPtr camera = this->getCamera();
440  if (!camera)
441  return;
442  CameraInfo cam_old(camera);
443 
444  // When viewing a scene of 10mm size, errors of 0.01mm are noticeable.
445  // Keep tolerance well below this level:
446  double tol = 0.001;
447  if (similar(cam_old, info, tol))
448  return; // break update loop: this event is triggered by camera change.
449 
450 // this->handleLights();
451 
452  mBlockCameraUpdate = true;
453  camera->SetPosition(info.pos.begin());
454  camera->SetFocalPoint(info.focus.begin());
455  camera->SetViewUp(info.vup.begin());
456  camera->SetViewAngle(info.viewAngle);
457  // use 2m, as the camera sometimes can move far from the object during zoom
458  camera->SetClippingRange(1, std::max<double>(2000, info.distance() * 10));
459  mBlockCameraUpdate = false;
460 }
461 
465 
467 {
468  currentValue = 0;
469  range = DoubleRange(0,0,0.1);
470 }
471 
472 double JitterFilter::newValue(double value)
473 {
474  // If outside range:
475  // reset interval and return value.
476  if (( value<=range.min() )||( value>=range.max() ))
477  {
478  double minimumInterval = 5.0;
479  double interval = std::min(minimumInterval, value/20);
480  double level = 0;
481 
482  if (value<range.min())
483  level = value + interval/2;
484  else if (value>range.max())
485  level = value - interval/2;
486 
487  range = DoubleRange(level-interval/2, level+interval/2, interval/10);
488 
489  currentValue = value;
490  }
491  return currentValue;
492 }
493 
497 
499 {
500  pos = Vector3D(camera->GetPosition());
501  focus = Vector3D(camera->GetFocalPoint());
502  vup = Vector3D(camera->GetViewUp());
503  viewAngle = camera->GetViewAngle();
504 }
505 
506 bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
507 {
508  return (similar(lhs.pos, rhs.pos, tol) &&
509  similar(lhs.focus, rhs.focus, tol) &&
510  similar(lhs.vup, rhs.vup, tol) &&
511  similar(lhs.viewAngle, rhs.viewAngle, tol)
512  );
513 }
514 
515 }//namespace cx
PlainObject normal() const
static Vector3D findCameraPosOnLineFixedDistanceFromFocus(Vector3D p_line, Vector3D e_line, double distance, Vector3D focus)
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
void setCameraStyle(CameraStyleData style)
Utility class for describing a bounded numeric range.
Definition: cxDoubleRange.h:32
boost::shared_ptr< class View > ViewPtr
DoubleBoundingBox3D getBox(Transform3D qMd=Transform3D::Identity())
Display a Tool in 3D.
Definition: cxToolRep3D.h:51
Listens to changes in viewport and camera matrix.
Vector3D cross(const Vector3D &a, const Vector3D &b)
compute cross product of a and b.
Definition: cxVector3D.cpp:41
boost::shared_ptr< class Data > DataPtr
void activeToolChanged(const QString &uId)
Transform3D createTransformIJC(const Vector3D &ivec, const Vector3D &jvec, const Vector3D &center)
vtkSmartPointer< class vtkRenderer > vtkRendererPtr
CameraStyleData getCameraStyle()
double newValue(double value)
static DoubleBoundingBox3D zero()
boost::shared_ptr< REP > findFirst(ToolPtr tool)
#define CX_LOG_CHANNEL_DEBUG(channel)
Definition: cxLogger.h:107
Transform3D createTransformTranslate(const Vector3D &translation)
void setView(ViewPtr widget)
double dot(const Vector3D &a, const Vector3D &b)
compute inner product (or dot product) of a and b.
Definition: cxVector3D.cpp:46
boost::shared_ptr< class RegionOfInterestMetric > RegionOfInterestMetricPtr
Representation of a floating-point bounding box in 3D. The data are stored as {xmin,xmax,ymin,ymax,zmin,zmax}, in order to simplify communication with vtk.
Eigen::Vector3d Vector3D
Vector3D is a representation of a point or vector in 3D.
Definition: cxVector3D.h:42
RegionOfInterest getROI() const
Vector3D center() const
void report(QString msg)
Definition: cxLogger.cpp:69
void debugPrint(CameraInfo info)
double distance() const
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
boost::shared_ptr< class CoreServices > CoreServicesPtr
Definition: cxCameraStyle.h:37
Listens to the start render event in a vtkRenderer.
boost::shared_ptr< class ToolRep3D > ToolRep3DPtr
static Vector3D elevateCamera(double angle, Vector3D camera, Vector3D focus, Vector3D vup)
static Vector3D orthogonalize_vup(Vector3D vup, Vector3D vpn, Vector3D vup_fallback)
static Vector3D findCameraPosByZoomingToROI(double viewAngle_vertical, double viewAngle_horizontal, Vector3D focus, Vector3D vup, Vector3D vpn, const DoubleBoundingBox3D &bb)
CameraStyleForView(CoreServicesPtr backend)
#define M_PI
Vector3D vpn() const
vtkSmartPointer< class vtkCamera > vtkCameraPtr
Namespace for all CustusX production code.
boost::shared_ptr< class Tool > ToolPtr