Open Inventor FAQs - Camera

I would like to be able to rotate the camera around an object, the way the bottom wheel does it in the examiner viewer. Unfortunately, the Inventor Mentor provides code only for moving the camera in the x or y direction, but not actually for rotating it. Do you have any source code for the rotation?

Sorry, but we don't currently have any source code that shows a rotation.

However, it would seem to be a straightforward operation. Use the pointAt method of SoCamera to specify that the camera should point at (the center of) the object, and also modify the position field of the SoCamera to circle around the object. The basic equations for calculating the position, which I'm sure you're already familiar with, are:

x = r*cos(theta)
 y = r*sin(theta)

where r is the distance from the object. If the object is not at the origin, then you would need to apply an appropriate offset to x and y.

How come my camera (or other object) isn't rotating the way I expect?

It is a very common mistake not to notice that when using the setValue method of an SoSFRotation field, if only four floating point values are used as parameters, they are interpreted as values of a quaternion, which is usually not what you had in mind. If you want the values to be interpreted as the vector description of an axis and then a rotation angle about that axis, you must pass the four values as one SbVec3f and one float. See the help file description for SoSFRotation:

setValue( const SbVec3f &axis, float angle ) 

See also the Viewer question about "point of focus".

How can I make a function that pans the current view in the x and y direction relative to the screen? I tried it with camera.position but if the user reorients the scene with the mouse, the movement does not occur along the screen's x and y coordinates.

Changing the camera's position field is the right strategy, you just have to make sure you move in a direction perpendicular to the direction the camera is pointing. Inventor viewers do this for mouse panning by projecting the starting and current cursor positions onto the camera focal plane and applying the difference to the camera position. For programmatic panning you can fetch the camera orientation matrix from the view volume and treat the rows as unit basis vectors. Example follows:

void OnPanView( PanDirection direction, float speed )
  // Note: Pan directions are currently set up so "pan left" means the
  //       object appears to move left, and so on.
  //       Speed is 1.0 by default.  Caller might pass higher number
  //       is Shift key is pressed and so on.

  SoCamera *camera = m_pExaminer->getCamera();
  if (camera) {
    // get the camera near plane height value
    float dist;
    if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
      float angle = ((SoPerspectiveCamera*)camera)->heightAngle.getValue();
      float length = camera->nearDistance.getValue();
      float length = camera->nearDistance.getValue();
      dist = length * ftan(angle);
    else if (camera->isOfType(SoOrthographicCamera::getClassTypeId()))
      dist = ((SoOrthographicCamera *)camera)->height.getValue();
      ASSERT( FALSE );

  // Pan increment is currently 2% of current view
  dist = dist * 0.02 * speed;

  // get camera right/left/up/down direction
  SbMatrix mx;
  mx = camera->orientation.getValue();
  SbVec3f dir;
  switch (direction) {
     case PAN_DOWN:
       dir.setValue(mx[1][0], mx[1][1], mx[1][2]);
    case PAN_UP:
      dir.setValue(-mx[1][0], -mx[1][1], -mx[1][2]);
      SbVec3f dir;

      switch (direction) {
        case PAN_DOWN:
          dir.setValue(mx[1][0], mx[1][1], mx[1][2]);

        case PAN_UP:
          dir.setValue(-mx[1][0], -mx[1][1], -mx[1][2]);

        case PAN_LEFT:
          dir.setValue(mx[0][0], mx[0][1], mx[0][2]);
          dist *= camera->aspectRatio.getValue();

        case PAN_RIGHT:
          dir.setValue(-mx[0][0], -mx[0][1], -mx[0][2]);
          dist *= camera->aspectRatio.getValue();
    // finally reposition the camera
    camera->position = camera->position.getValue() + dist * dir;

How I can place an object in the scene graph (for example, a color legend) that doesn't move when I move the main geometry?

The simplest way is to create a second camera, put it in the scene graph after all the "normal" geometry, then put the screen aligned geometry (legend or whatever) after the second camera. Inventor viewers always attach to the first camera they find in the scene graph, so that is the one that will be moved when the user manipulates the viewer.

So the scene graph would look something like this:

DEF Root Separator {
  DEF Scene Separator {
    PerspectiveCamera {}
    # Scene nodes go here
  DEF Legend Separator {
    OrthographicCamera {}
    # Legend nodes go here
    ResetTransform {
      whatToReset BOUNDINGBOX

Why an OrthographicCamera? Because it is the most convenient for drawing "2D" geometry. By default an OrthographicCamera maps the range -1 to 1 (in X and Y) to the viewport. So a legend positioned at (-.99,-.99,0) would be very close to the bottom left corner of the window. Note that you would probably want to set the camera's viewportMapping field to LEAVE_ALONE so the position tracks the window, not the viewport.

Why a ResetTransform node? Because the viewAll operation gets the bounding box of the entire scene graph, including the legend. If the legend geometry is very small with respect to the "real" scene, it may not matter. But in general the legend geometry will cause the viewer to rotate the camera around a point other than the geometric center of the "real" scene. There is no simple way to keep the GetBoundingBoxAction from considering the legend geometry, but we can use a ResetTransform node to reset the bounding box before returning to the legend's parent node.

Generally both cameras should be in the scene graph when you give the scene graph to the viewer (with setSceneGraph()). The viewer will attach to the first camera it finds and if that was the OrthographicCamera then the "real" scene might not be visible and definitely would not move when the camera moves.

Note: When the viewer finds a camera already in the scene graph it assumes that it should use the fields of that camera "as is". So it does not do the normal viewAll operation that is done when the viewer creates the camera. You may want to do an explicit viewAll after setting the scene graph. If so, note that this new camera position will not automatically be the "home" position. So typically your code would be something like:

// Existing call
pViewer->setSceneGraph( pRoot );

// Two new calls

This gives the same behavior as expected when giving the viewer a scene graph with no camera.

Alternatively you can give the scene graph to the viewer with no cameras in it, which causes the viewer to create a camera, then add the OrthographicCamera and associated nodes at the appropriate place.

I've read a file into the SceneViewer, and when I use the viewer controls to move the camera, some of the geometry moves and some of it doesn't.

This could happen if the scene graph has multiple cameras in it. Inventor viewers always attach to the first camera they find in the scene graph, so that is the one that will be moved when the user manipulates the camera. Geometry that is under the second, and subsequent, cameras will not move.

How do I map mouse coordinates back to 3D coordinates?

If you're on Windows, take a look at the example in .../examples/IVF/BoxZoom. If not, well you're on the right track, but there are a couple quirks that are not obvious from the current documentation. Here is the relevant fragment (using some IVF calls, but we think the important bit about how to get the correct view volume is clear enough):

// Collect info about current viewport
SbViewportRegion vpRegion = IvfGetViewportRegion();
float vpAspect = vpRegion.getViewportAspectRatio();

// Collect info about current view volume
// Note: In ADJUST_CAMERA mode, Inventor automatically modifies the
// view volume based on the aspect ratio of the viewport.
// However the view volume  values are not changed, only the
// values passed to OpenGL. In order to get the modified
// values (ie. the actual view volume used to render) we must
// pass the viewport aspect ratio to getViewVolume.
// Note: In ADJUST_CAMERA mode, when the viewport aspect ratio is
// less than 1, Inventor also automatically scales the view
// volume by 1/vpAspect. Unfortunately getViewVolume does
// not apply this adjustment, so we have to do it ourselves.

SoCamera *pCam = IvfGetActiveCamera();
SbViewVolume viewVol = pCam->getViewVolume( vpAspect );
if (vpAspect < 1.0)
  viewVol.scale(1.0 / vpAspect);