ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
vtkPVImageInteractorStyle.cxx
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <vtkCallbackCommand.h>
11 #include <vtkCamera.h>
12 #include <vtkCommand.h>
13 #include <vtkImageData.h>
14 #include <vtkImageSlice.h>
15 #include <vtkImageSliceMapper.h>
16 #include <vtkMath.h>
17 #include <vtkMatrix4x4.h>
18 #include <vtkObjectFactory.h>
19 #include <vtkPropCollection.h>
20 #include <vtkRenderWindowInteractor.h>
21 #include <vtkRenderer.h>
22 #include <vtkTransform.h>
23 
25 
26 //-------------------------------------------------------------------------
28  this->RotationFactor = 1.0;
29 }
30 
31 //-------------------------------------------------------------------------
33 
34 //-------------------------------------------------------------------------
35 void vtkPVImageInteractorStyle::PrintSelf(ostream& os, vtkIndent indent) {
36  this->Superclass::PrintSelf(os, indent);
37  os << indent << "RotationFactor: " << this->RotationFactor << "\n";
38 }
39 
40 //-------------------------------------------------------------------------
42  // Left button: Pan (translate)
43  // Don't call base class to avoid conflicts
44  int x = this->Interactor->GetEventPosition()[0];
45  int y = this->Interactor->GetEventPosition()[1];
46 
47  this->FindPokedRenderer(x, y);
48  if (!this->CurrentRenderer) {
49  return;
50  }
51 
52  this->GrabFocus(this->EventCallbackCommand);
53  this->StartPan();
54 }
55 
56 //-------------------------------------------------------------------------
58  // End pan
59  if (this->Interactor) {
60  if (this->State == VTKIS_PAN) {
61  this->EndPan();
62  }
63  this->ReleaseFocus();
64  }
65 }
66 
67 //-------------------------------------------------------------------------
69  // Middle button: Rotate around Z-axis (perpendicular to image plane)
70  // Don't call base class to avoid conflicts
71  int x = this->Interactor->GetEventPosition()[0];
72  int y = this->Interactor->GetEventPosition()[1];
73 
74  this->FindPokedRenderer(x, y);
75  if (!this->CurrentRenderer) {
76  return;
77  }
78 
79  this->GrabFocus(this->EventCallbackCommand);
80  this->StartRotate();
81 }
82 
83 //-------------------------------------------------------------------------
85  // End rotate
86  if (this->Interactor) {
87  if (this->State == VTKIS_ROTATE) {
88  this->EndRotate();
89  }
90  this->ReleaseFocus();
91  }
92 }
93 
94 //-------------------------------------------------------------------------
96  if (this->Interactor) {
97  if (this->State == VTKIS_PAN) {
98  // Pan: translate camera
99  this->Pan();
100  } else if (this->State == VTKIS_ROTATE) {
101  // Rotate: rotate around Z-axis (perpendicular to image plane)
102  this->Rotate();
103  } else {
104  vtkInteractorStyleImage::OnMouseMove();
105  }
106  }
107 }
108 
109 //-------------------------------------------------------------------------
111  if (this->CurrentRenderer == nullptr || this->Interactor == nullptr) {
112  return;
113  }
114 
115  vtkRenderWindowInteractor* rwi = this->Interactor;
116  vtkCamera* camera = this->CurrentRenderer->GetActiveCamera();
117 
118  // Compute display center for rotation calculation
119  int* size = this->CurrentRenderer->GetSize();
120  double displayCenter[2];
121  displayCenter[0] = size[0] / 2.0;
122  displayCenter[1] = size[1] / 2.0;
123 
124  // Calculate vectors from center to mouse positions
125  int x1 =
126  rwi->GetLastEventPosition()[0] - static_cast<int>(displayCenter[0]);
127  int x2 = rwi->GetEventPosition()[0] - static_cast<int>(displayCenter[0]);
128  int y1 =
129  rwi->GetLastEventPosition()[1] - static_cast<int>(displayCenter[1]);
130  int y2 = rwi->GetEventPosition()[1] - static_cast<int>(displayCenter[1]);
131 
132  // Check for zero vectors to avoid division by zero
133  if ((x2 == 0 && y2 == 0) || (x1 == 0 && y1 == 0)) {
134  return;
135  }
136 
137  // Calculate rotation angle using cross product (similar to
138  // vtkPVTrackballRoll) This allows continuous rotation
139  double len1 = sqrt(static_cast<double>(x1 * x1 + y1 * y1));
140  double len2 = sqrt(static_cast<double>(x2 * x2 + y2 * y2));
141 
142  if (len1 == 0.0 || len2 == 0.0) {
143  return;
144  }
145 
146  // Calculate angle using cross product: sin(angle) = (x1*y2 - y1*x2) / (|v1|
147  // * |v2|) Reduce rotation speed by multiplying with a factor (0.1 for
148  // smoother rotation)
149  double angle =
150  vtkMath::DegreesFromRadians((x1 * y2 - y1 * x2) / (len1 * len2)) *
151  0.1 * this->MotionFactor * this->RotationFactor;
152 
153  // Compute rotation axis (view direction for 2D images)
154  double axis[3];
155  double* pos = camera->GetPosition();
156  double* fp = camera->GetFocalPoint();
157  axis[0] = fp[0] - pos[0];
158  axis[1] = fp[1] - pos[1];
159  axis[2] = fp[2] - pos[2];
160  vtkMath::Normalize(axis);
161 
162  // Calculate image center dynamically from image slice bounds
163  // This ensures rotation center is always the image center, even after
164  // panning
165  double center[3] = {0.0, 0.0, 0.0};
166  bool centerFound = false;
167 
168  // Find image slice and get its geometric center
169  vtkPropCollection* props = this->CurrentRenderer->GetViewProps();
170  props->InitTraversal();
171  vtkProp* prop = nullptr;
172  while ((prop = props->GetNextProp()) != nullptr) {
173  vtkImageSlice* imageSlice = vtkImageSlice::SafeDownCast(prop);
174  if (imageSlice && imageSlice->GetVisibility()) {
175  // Get bounds of the image slice in world coordinates
176  double bounds[6];
177  imageSlice->GetBounds(bounds);
178 
179  // Check if bounds are valid and non-degenerate
180  if (bounds[0] < bounds[1] && bounds[2] < bounds[3]) {
181  // Calculate geometric center from bounds
182  center[0] = (bounds[0] + bounds[1]) * 0.5;
183  center[1] = (bounds[2] + bounds[3]) * 0.5;
184  center[2] = (bounds[4] + bounds[5]) * 0.5;
185  centerFound = true;
186  break; // Use first valid image slice
187  }
188  }
189  }
190 
191  // If no image slice found, use camera focal point as fallback
192  if (!centerFound) {
193  camera->GetFocalPoint(center);
194  }
195 
196  // Apply rotation using transform (allows continuous rotation)
197  vtkTransform* transform = vtkTransform::New();
198  transform->Identity();
199  transform->Translate(center[0], center[1], center[2]);
200  transform->RotateWXYZ(angle, axis[0], axis[1], axis[2]);
201  transform->Translate(-center[0], -center[1], -center[2]);
202 
203  camera->ApplyTransform(transform);
204  camera->OrthogonalizeViewUp();
205  transform->Delete();
206 
207  if (this->AutoAdjustCameraClippingRange) {
208  this->CurrentRenderer->ResetCameraClippingRange();
209  }
210 
211  if (rwi->GetLightFollowCamera()) {
212  this->CurrentRenderer->UpdateLightsGeometryToFollowCamera();
213  }
214 
215  rwi->Render();
216 }
217 
218 //-------------------------------------------------------------------------
220  if (this->CurrentRenderer == nullptr || this->Interactor == nullptr) {
221  return;
222  }
223 
224  vtkRenderWindowInteractor* rwi = this->Interactor;
225  vtkCamera* camera = this->CurrentRenderer->GetActiveCamera();
226 
227  // For parallel projection (2D images), use optimized panning
228  if (camera->GetParallelProjection()) {
229  camera->OrthogonalizeViewUp();
230  double up[3], vpn[3], right[3];
231  camera->GetViewUp(up);
232  camera->GetViewPlaneNormal(vpn);
233  vtkMath::Cross(vpn, up, right);
234 
235  int* size = this->CurrentRenderer->GetSize();
236  int dx = rwi->GetEventPosition()[0] - rwi->GetLastEventPosition()[0];
237  int dy = rwi->GetLastEventPosition()[1] -
238  rwi->GetEventPosition()[1]; // Y is flipped
239 
240  double scale = camera->GetParallelScale();
241  double panX = (double)dx / (double)size[1] * scale * 2.0;
242  double panY = (double)dy / (double)size[1] * scale * 2.0;
243 
244  double pos[3], fp[3];
245  camera->GetPosition(pos);
246  camera->GetFocalPoint(fp);
247 
248  double tmp;
249  tmp = (right[0] * panX + up[0] * panY);
250  pos[0] += tmp;
251  fp[0] += tmp;
252  tmp = (right[1] * panX + up[1] * panY);
253  pos[1] += tmp;
254  fp[1] += tmp;
255  tmp = (right[2] * panX + up[2] * panY);
256  pos[2] += tmp;
257  fp[2] += tmp;
258 
259  camera->SetPosition(pos);
260  camera->SetFocalPoint(fp);
261  } else {
262  // For perspective projection, use standard panning
263  double focalPoint[4], pickPoint[4], prevPickPoint[4];
264  double z;
265 
266  camera->GetFocalPoint(focalPoint);
267  focalPoint[3] = 1.0;
268 
269  this->ComputeWorldToDisplay(this->CurrentRenderer, focalPoint[0],
270  focalPoint[1], focalPoint[2], focalPoint);
271  z = focalPoint[2];
272 
273  this->ComputeDisplayToWorld(
274  this->CurrentRenderer, rwi->GetLastEventPosition()[0],
275  rwi->GetLastEventPosition()[1], z, prevPickPoint);
276 
277  this->ComputeDisplayToWorld(this->CurrentRenderer,
278  rwi->GetEventPosition()[0],
279  rwi->GetEventPosition()[1], z, pickPoint);
280 
281  // Camera motion is reversed
282  camera->SetFocalPoint(
283  focalPoint[0] - (pickPoint[0] - prevPickPoint[0]),
284  focalPoint[1] - (pickPoint[1] - prevPickPoint[1]),
285  focalPoint[2] - (pickPoint[2] - prevPickPoint[2]));
286 
287  camera->SetPosition(
288  camera->GetPosition()[0] - (pickPoint[0] - prevPickPoint[0]),
289  camera->GetPosition()[1] - (pickPoint[1] - prevPickPoint[1]),
290  camera->GetPosition()[2] - (pickPoint[2] - prevPickPoint[2]));
291  }
292 
293  if (this->AutoAdjustCameraClippingRange) {
294  this->CurrentRenderer->ResetCameraClippingRange();
295  }
296 
297  if (rwi->GetLightFollowCamera()) {
298  this->CurrentRenderer->UpdateLightsGeometryToFollowCamera();
299  }
300 
301  rwi->Render();
302 }
int size
ParaView-style interactor for 2D image viewing.
~vtkPVImageInteractorStyle() override
void PrintSelf(ostream &os, vtkIndent indent) override
normal_z y
normal_z x
normal_z z
CLOUDVIEWER_HOST_DEVICE float Cross(const Point &a, const Point &b)
Definition: IoUImpl.h:39
vtkStandardNewMacro(vtkPVImageInteractorStyle)