Rotation of camera in 3D view without random tilting

Hi everyone,

as some of you might feel as I do that the camera in 3D view tilts when rotating, I have rewritten the code in Slicer/Libs/MRML/DisplayableManger/vtkMRMLCameraWidget.cxx in vtkMRMLCameraWidget::ProcessRotate as the following and then built my own Slicer.

//----------------------------------------------------------------------------
bool vtkMRMLCameraWidget::ProcessRotate(vtkMRMLInteractionEventData* eventData)
{
  if (!this->Renderer || !eventData)
    {
    return false;
    }

  const int* eventPosition = eventData->GetDisplayPosition();
  int dx = eventPosition[0] - this->PreviousEventPosition[0];
  int dy = eventPosition[1] - this->PreviousEventPosition[1];
  if (dx == 0 && dy == 0)
    {
    return true;
    }

  const int *size = this->Renderer->GetRenderWindow()->GetSize();

  double delta_elevation = -20.0 / size[1];
  double delta_azimuth = -20.0 / size[0];

  double rxf = (double)dx * delta_azimuth * this->MotionFactor;
  double ryf = (double)dy * delta_elevation * this->MotionFactor;

  vtkCamera* camera = this->GetCamera();
  if (!camera)
    {
    return false;
    }

  bool wasCameraNodeModified = this->CameraModifyStart();

  if (this->CameraTiltLocked == true)
    {
    camera->Azimuth(rxf);
    }
  else
    {
    const double* cameraPosition = camera->GetPosition();
    const double* cameraFocalPoint = camera->GetFocalPoint();
    const double* cameraViewUp = camera->GetViewUp();
    bool upside_down = cameraViewUp[2] < 0;
    double upside_down_factor = upside_down ? -1.0 : 1.0;

    double P[3] = {
        cameraPosition[0] - cameraFocalPoint[0],
        cameraPosition[1] - cameraFocalPoint[1],
        cameraPosition[2] - cameraFocalPoint[2]
    };

    double H = sqrt(P[0] * P[0] + P[1] * P[1]);
    double elev = atan2(P[2], H);

    double sin_elev = sin(elev);
    double azi;
    if (abs(sin_elev) < 0.8) {
      azi = atan2(P[1], P[0]);
    } else {
      if (sin_elev < -0.8) {
          azi = atan2(upside_down_factor * cameraViewUp[1], upside_down_factor * cameraViewUp[0]);
      } else {
          azi = atan2(-upside_down_factor * cameraViewUp[1], -upside_down_factor * cameraViewUp[0]);
      }
    }
    double  D = sqrt(P[0] * P[0] + P[1] * P[1]  + P[2] * P[2]);
    double azi_new = azi + rxf / 60.0;
    double elev_new = elev + upside_down_factor * ryf / 60.0;
    double Hnew = D * cos(elev_new);

    double Pnew[3] = {
        Hnew * cos(azi_new),
        Hnew * sin(azi_new),
        D * sin(elev_new)
    };

    double up_z = upside_down_factor * cos(elev_new);
    double up_h = upside_down_factor * sin(elev_new);

    double up_new[3] = {
        -up_h * cos(azi_new),
        -up_h * sin(azi_new),
        up_z
    };

    double new_pos[3] = {
        cameraFocalPoint[0] + Pnew[0],
        cameraFocalPoint[1] + Pnew[1],
        cameraFocalPoint[2] + Pnew[2]
    };

    camera->SetViewUp(up_new);
    camera->SetPosition(new_pos);
    }
  camera->OrthogonalizeViewUp();
  this->CameraModifyEnd(wasCameraNodeModified, true, true);

  this->PreviousEventPosition[0] = eventPosition[0];
  this->PreviousEventPosition[1] = eventPosition[1];

  return true;
}

I particularly changed the code after

  if (this->CameraTiltLocked == true)
    {
    camera->Azimuth(rxf);
    }
  else
    {  # changes are made here

I hope you will find it usefull as well :slight_smile:

The code was rewritten to C++ based on this py-file:
https://github.com/RubendeBruin/DAVE/blob/d0dec40c9a2e9416f31edabfeaf1f769fb6eca51/src/DAVE/visual_helpers/vtkBlenderLikeInteractionStyle.py#L4

1 Like

Thank you for raising this and providing an example of an additional method for constraining camera rotation in the 3D view.

The camera’s view-up direction may indeed unintentionally spin as the camera is rotated around. Although it is quite easy to fix it by either moving the mouse in a circle or spinning the view using Ctrl + Click-and-drag, it is even better if it can be avoided in the first place. The “Tilt lock” option was added for exactly this reason, but it may be a bit too restrictive to not let users to change the view-up direction at all.

Your code snippet is nice in that it allows tilting but not spinning of the view-up direction, this allows some more freedom than completely locking the view-up direction, but more controlled than letting anything rotated anywhere. However, the current impmlementation you shared has the patient IS axis hardcoded to be the view-up direction. This is great if you want the patient S direction to point upwards, but extremely annoying when you want to make some other axis to point upwards - for example to show a lateral view of the patient (as lying on the CT table). This view orientation is impossible to achieve with code you proposed:

We cannot remove the current free motion and probably want to keep the “tilt lock” option as well, but we could add a third “Spin lock” option. It would do almost what you implemented, but instead of always keeping the patient IS axis as view-up, it would keep an arbitrarily chosen direction as view-up. The “arbitrarily chosen direction” would actually be the view-up direction at the time when “Spin lock” is activated.

If you can make this improvement we would be happy to add this “Spin lock” option to Slicer core.

1 Like

Hi @lassoan,

yes, you are right that my code is somewhat restricted. I had not noticed before the issue that you addressed. I will try to add the feature you said.

Thanks!

1 Like