Changing rotation center for orthographic camera

I want to change the center of rotation of an orthographic camera in open inventor. What can I do?

When you say “center of rotation” I’m assuming that you are talking about the point that the ExaminerViewer rotates the camera around. This point is implicitly defined by the camera node’s position, orientation and focalDistance fields. Specifically the center of rotation is located focalDistance units away from the camera position, along the view vector (-Z axis of the camera’s local coordinate system). For example, if the camera position is 0,0,5, the orientation is identity (no rotation) and the focalDistance is 10, then the center of rotation is located at 0,0,-5.

-Mike

Dear Mike,

How can I change the center so that at first the camera is not moved and the object is in the middle?

When I use the following code, the camera is moved from the center which is not desirable:

center = SbVec3f(1, 1, 1)
focalDistance = (center - camera.position.getValue()).length()
camera.focalDistance.setValue( focalDistance )
camera.pointAt(center)

Are you sure the camera is moved by this code???
I’m quite sure the pointAt() method only changes the camera’s orientation field…

“center” is the center of the object you want the camera to look at, right? If so, then this seems like the right code to do what you want. Have you examined the camera position before and after to confirm that it changed?

-Mike

You’re right. I meant that the camera orientation is changed. Is there any way to change the center of rotation without changing the camera orientation and position? I want the model to stay exactly in the middle when the code is run, but its center of rotation not be the center of model. Can it be done with any other method such as combination of transformation matrices instead of manipulating camera properties?

I think the short answer is No. :slight_smile: The definition of “center of rotation” is focalDistance units from the camera position along the view vector (orientation). So the center of rotation can be any point in space, but unless the desired point happens to be somewhere along the current view vector, then you have to change the camera orientation to make that point the center of rotation.

I think I understand the visual effect you want, but I’m not sure why. You’re saying you want the model initially centered in the window and when the user clicks/drags the mouse the model appears to rotate around some point other than the “center” of the model. You understand this will generally rotate some or all the model out of the center of the window, right?

You can certainly do this by implementing your own user interface (explicit slider, catching and handling mouse events, etc) and applying a transformation matrix to rotate the model. Note that the viewer’s default UI always moves/rotates the camera - it just “looks like” the model is rotating. You can also override/replace the viewer’s UI using the SoWinFullViewer addViewingMouseBinding() method and implementing your class derived from SoViewingFunction.

FYI, there is a dedicated forum for Open Inventor developers here: www.openinventor.net. Other folks might have additional suggestions.

R,
Mike

Dear Mike,

Could you please send a simple code snipped demonstrating the method that you mentioned? Suppose the shape is a default cube displayed at the center but rotating around (1,1,1).

Thanks a lot for too much help,
artmansoft

The following trivial OIV program displays a wireframe cube, a red sphere at 0,0,0 and a green sphere at 1,1,1. The spheres are so you can see that the camera is always pointed at 0,0,0 and the cube rotates around 1,1,1. Press any key to rotate the cube one increment.


// Rotate geometry around an arbitrary point

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>

#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoTransform.h>

#include <Inventor/events/SoKeyboardEvent.h> 

/////////////////////////////////////////////////////////////

void EventCB(void *userData, SoEventCallback *node )
{
  SoTransform *pTran = (SoTransform*)userData;
  SbVec3f axis;
  float   angle;
  pTran->rotation.getValue( axis, angle );
  axis[0] += 0.1f;
  axis[1]  = 1;
  axis[2] -= 0.1f;
  angle += 0.1f;
  if (angle > (2*3.14159265)) angle -= (2*3.14159265f);
  pTran->rotation.setValue( axis, angle );
  pTran->center.setValue( 1, 1, 1 );
}

/////////////////////////////////////////////////////////////

void
main(int argc, char **argv)
{
  HWND myWindow = SoXt::init(argv[0]);

  // "Markers" to show where 0,0,0 and 1,1,1 are
  SoTransform *pTrn2 = new SoTransform();
  SoMaterial  *pMat1 = new SoMaterial();
  SoMaterial  *pMat2 = new SoMaterial();
  SoSphere    *pSph1 = new SoSphere();
  SoSphere    *pSph2 = new SoSphere();
  pTrn2->translation  = SbVec3f(1,1,1);
  pMat1->diffuseColor = SbColor(1,0,0);
  pMat2->diffuseColor = SbColor(0,1,0);
  pSph1->radius = 0.1f;
  pSph2->radius = 0.1f;
  SoSeparator *pMarkSep = new SoSeparator();
  pMarkSep->addChild( pMat1 );
  pMarkSep->addChild( pSph1 );
  pMarkSep->addChild( pMat2 );
  pMarkSep->addChild( pTrn2 );
  pMarkSep->addChild( pSph2 );

  // Geometry
  SoTransform *pTrn3 = new SoTransform();
  SoMaterial  *pMat3 = new SoMaterial();
  SoCube      *pCube = new SoCube();
  SoDrawStyle *pStyl = new SoDrawStyle();
  pMat3->diffuseColor = SbColor(0,0,1);
  pStyl->style = SoDrawStyle::LINES;
  pStyl->lineWidth = 3;
  SoSeparator *pCubeSep = new SoSeparator();
  pCubeSep->addChild( pMat3 );
  pCubeSep->addChild( pStyl );
  pCubeSep->addChild( pTrn3 );
  pCubeSep->addChild( pCube );

  // Assemble scenegraph
  SoSeparator *pRoot = new SoSeparator();
  pRoot->addChild( pMarkSep );
  pRoot->addChild( pCubeSep );
  SoEventCallback *pEvtCB = new SoEventCallback();
  pEvtCB->addEventCallback( SoKeyboardEvent::getClassTypeId(), EventCB, (void*)pTrn3 );
  pRoot->insertChild( pEvtCB, 0 );

  // Create viewer
  SoXtExaminerViewer *pViewer = new SoXtExaminerViewer(myWindow);
  pViewer->setSceneGraph( pRoot );
  pViewer->setBackgroundColor( SbColor(0,0.2f,0.2f) );
  pViewer->setViewing( FALSE ); // Ready to process key events
  pViewer->setTitle( "Press any key to rotate..." );
  pViewer->show();

  // Loop, then cleanup
  SoXt::show(myWindow);
  SoXt::mainLoop();
  delete pViewer;
  SoXt::finish();
}

Dear Mike,

Thank you very much for the code!!! That’s exactly the behavior that what I wanted. Now I see that I do not have even a bit of your knowledge in this field.

Could you please change the event, so that it starts to rotate with mouse buttons? I’m not talking about changing the SoKeyboardEvent to SoMouseButtonEvent, but it’s about overriding the processSoEvent of the viewer, so that spinning the model by mouse does exactly the same behavior but in any direction that mouse moves. Panning also must not change the center of rotation. That is, it works the same in all positions. In this case setViewing is not FALSE. I think it is possible to define our own mouse event too when the viewing is False?

Could you please do this so great favor to me? I’ll be really grateful! Please help me 'cause I’m in the hell without it.

Very Thanks in advance,
artmansoft

Yes, it’s possible to subclass viewers and override event handling, but there are a lot of issues to consider and I don’t have time right now to figure that out again. :slight_smile:
What I do for test/demo programs is use the viewer’s “raw event” callback to hijack events before the viewer even sees them. You don’t actually have to deal with system events (unless you want to) because you can use the SoXXXDevice classes to translate the system events into SoEvent objects. You can then use an SbSphereSheetProjector to convert mouse motion into incremental rotation (this is exactly what the Examiner viewer does to rotate the camera). In this example left-mouse rotates the cube around the point 1,1,1. Pressing any key resets the cube transformation.

-Mike


// Rotate geometry around an arbitrary point
// Use leftMouse to rotate, press any key to reset transform.

#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>

#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoTransform.h>

#include <Inventor/events/SoKeyboardEvent.h> 
#include <Inventor/events/SoMouseButtonEvent.h> 
#include <Inventor/events/SoLocation2Event.h> 
#include <Inventor/projectors/SbSphereSheetProjector.h> 

#include <Inventor/Win/devices/SoWinKeyboard.h> 
#include <Inventor/Win/devices/SoWinMouse.h> 

/////////////////////////////////////////////////////////////

// Global state used by callback functions
// (TODO: Make an application state class)
//
// buttonDown    - Tracks state of mouse button (up or down)
// gpViewer      - Need viewer to get current viewport
//                 (changes if window is resized)
// gpTransform   - Geometry transform node to modify
// gpSphereSheet - Sphere sheet projector to convert mouse
//                 motion into 3D rotation

static bool                    buttonDown    = false;
static SoXtExaminerViewer     *gpViewer      = NULL;
static SoTransform            *gpTransform   = NULL;
static SbSphereSheetProjector *gpSphereSheet = NULL;

// Handle key press events
void keyEvent( const SoEvent *event )
{
  // On any key press, reset geometry transformation
  gpTransform->rotation.setValue( SbVec3f(0,0,1), 0 );
}

// Handle mouse press/release and move events
void mouseEvent( const SoEvent *pEvent )
{
  // If first time, create the sphere sheet projector.
  // In this case we can use a simple orthographic view volume.
  if (! gpSphereSheet) {
    SbViewVolume vv;
    vv.ortho( -1, 1, -1, 1, -10, 10 );
    gpSphereSheet = new SbSphereSheetProjector;
    gpSphereSheet->setViewVolume( vv );
    gpSphereSheet->setSphere( SbSphere(SbVec3f(0, 0, 0), .7f) );
  }

  // Get the event position in normalized coordinates [0..1]
  SbVec2f pos = pEvent->getNormalizedPosition( gpViewer->getViewportRegion() );

  // Update the sphere sheet projector and get incremental rotation.
  SbRotation rot;
  gpSphereSheet->projectAndGetRotation( pos, rot );

  // On mouse press and release just remember the state change
  if (SO_MOUSE_PRESS_EVENT( pEvent, BUTTON1 )) {
    buttonDown = true;
  }
  else if (SO_MOUSE_RELEASE_EVENT( pEvent, BUTTON1 )) {
    buttonDown = false;
  }

  // Mouse move (only interesting if button is down)
  else {
    if (buttonDown) {
      // Set center of rotation to desired point on geometry
      gpTransform->center = SbVec3f(1,1,1);
      // Concatenate additional rotation
      gpTransform->rotation = gpTransform->rotation.getValue() * rot;
    }
  }
}

/////////////////////////////////////////////////////////////

// Classes to translate raw events into SoEvents
static SoXtKeyboard *gpKeybDevice  = NULL;
static SoXtMouse    *gpMouseDevice = NULL;

SbBool rawEventCB( void *userData, XAnyEvent *anyevent )
{
  // First time create the event translation objects
  if (! gpMouseDevice) {
    gpKeybDevice  = new SoWinKeyboard();
    gpMouseDevice = new SoWinMouse();
  }
  // If this translates to any kind of mouse event, handle it.
  // (Mouse device needs to window size to invert Y coords.)
  gpMouseDevice->setWindowSize( gpViewer->getViewportRegion().getViewportSizePixels() );
  const SoEvent *pMouseEvent = gpMouseDevice->translateEvent( anyevent );
  if (pMouseEvent) {
    mouseEvent( pMouseEvent );
    return TRUE;
  }
  // Else if this translates to any kind of keyboard event, handle it
  const SoEvent *pKeyEvent = gpKeybDevice->translateEvent( anyevent );
  if (pKeyEvent) {
    keyEvent( pKeyEvent );
    return TRUE;
  }
  return FALSE;
}

/////////////////////////////////////////////////////////////

void
main(int argc, char **argv)
{
  HWND myWindow = SoXt::init(argv[0]);

  // "Markers" to show where 0,0,0 and 1,1,1 are
  SoTransform *pTrn2 = new SoTransform();
  SoMaterial  *pMat1 = new SoMaterial();
  SoMaterial  *pMat2 = new SoMaterial();
  SoSphere    *pSph1 = new SoSphere();
  SoSphere    *pSph2 = new SoSphere();
  pTrn2->translation  = SbVec3f(1,1,1);
  pMat1->diffuseColor = SbColor(1,0,0);
  pMat2->diffuseColor = SbColor(0,1,0);
  pSph1->radius = 0.1f;
  pSph2->radius = 0.1f;
  SoSeparator *pMarkSep = new SoSeparator();
  pMarkSep->addChild( pMat1 );
  pMarkSep->addChild( pSph1 );
  pMarkSep->addChild( pMat2 );
  pMarkSep->addChild( pTrn2 );
  pMarkSep->addChild( pSph2 );

  // Geometry
  SoTransform *pTrn3 = new SoTransform();
  SoMaterial  *pMat3 = new SoMaterial();
  SoCube      *pCube = new SoCube();
  SoDrawStyle *pStyl = new SoDrawStyle();
  pMat3->diffuseColor = SbColor(0,0,1);
  pStyl->style = SoDrawStyle::LINES;
  pStyl->lineWidth = 3;
  SoSeparator *pCubeSep = new SoSeparator();
  pCubeSep->addChild( pMat3 );
  pCubeSep->addChild( pStyl );
  pCubeSep->addChild( pTrn3 );
  pCubeSep->addChild( pCube );

  // Assemble scenegraph
  SoSeparator *pRoot = new SoSeparator();
  pRoot->addChild( pMarkSep );
  pRoot->addChild( pCubeSep );

  // Create viewer
  SoXtExaminerViewer *pViewer = new SoXtExaminerViewer(myWindow);
  pViewer->setSceneGraph( pRoot );
  pViewer->setBackgroundColor( SbColor(0,0.2f,0.2f) );
  pViewer->setTitle( "LMoust to rotate, any key to reset..." );
  pViewer->show();

  // Global variables used in event callback
  gpViewer      = pViewer;  // Viewer (to get viewport etc)
  gpTransform   = pTrn3;    // Geometry transform to modify

  // Catch raw system events before they're handled by the viewer
  pViewer->setEventCallback( rawEventCB, NULL );

  // Loop, then cleanup
  SoXt::show(myWindow);
  SoXt::mainLoop();
  delete pViewer;
  SoXt::finish();
}

Dear Mike,

You said that:

Yes, it’s possible to subclass viewers and override event handling, but there are a lot of issues to consider and I don’t have time right now to figure that out again. :slight_smile:

In fact, I really need a subclass of viewer. I know that you’re busy, but it depends on my life at the moment! :sorrow:

Please do it whenever you have time, but not too late! :eek: Because I will be as good as dead at that time.

Could you please send also the pan and zoom functions with that hijacking method for the moment?

Very Thanks,
poor artmansoft

Meaning no offense, but it sounds like you need to hire an Open Inventor developer…

Good luck with your project.

-Mike

Dear Mike,

Could you please send at least the pan and zoom functions for the previous code?

Thanks,
artmansoft