Hi,

Here is my submission for a ViewportResizeHandler class that allows
you to resize/move a camera viewport in a way similar to "normal"
windows. The name is a bit of a misnomer since I added the move
functionality later. Here are some quirks that I found but happen only
when you do a complex combination of operations:

- the mouse cursor isn't always updated correctly when you
press/release the move key  while moving/resizing or while moving
between viewports. The latter case is because I think that the key
release events aren't being properly processed if you press the move
key in one viewport and release it in another (see next point).

- I also noticed that the osg event dispatch mechanism doesn't take
into account camera render order. This is a bit of problem in my
environment because I have set things up so that when an event
(mouse/key press) occurs over a viewport, the camera render order
changes so that that camera is drawn on top. However, the events are
still being processed in the order that the cameras were added to the
view so even though one camera viewport may be visible on top of
another, the lower one processes the event last.

These in my opinion are fairly minor issues and only occur when you
have multiple viewports and doing some complex operations. The 95% of
the things that you would typically want to do seem to work fine.

I have tried to keep as close as my knowledge to the osg coding style,
but if there are changes necessary let me know and I can make them.
The one necessary change is the windows dll declaration which is
needed to work in my environment.

Cheers,
Chris

Attachment: ViewportResizeHandler
Description: Binary data

/* -*-c++-*- OpenSceneGraph - Copyright (C) 2012 C. Skluzacek
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
*/

#include "ViewportResizeHandler"

#include <osgViewer/GraphicsWindow>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/GUIEventAdapter>

#include <map>

using namespace osgViewer;

template< typename T >
T clamp(T value, T low, T high)
{
  return std::min(std::max(value, low), high);
}

template< typename T >
bool in_range(T value, T low, T high)
{
  return value >= low && value <= high;
}

static ViewportResizeHandler::Zone getZone(const osgGA::GUIEventAdapter& ea, const osg::Viewport *vp, int zoneSize);

static void getOffsets(int mx, int my, const osg::Viewport *vp,
                       ViewportResizeHandler::Zone zone, int& offsetX, int& offsetY);

static void handleResize(const osgGA::GUIEventAdapter& ea, osg::Camera *camera, const osg::Viewport *vp, ViewportResizeHandler::Zone currentZone, int offsetX, int offsetY, int minWidth, int minHeight);

static osgViewer::GraphicsWindow::MouseCursor getZoneCursor( ViewportResizeHandler::Zone zone );

static void updateCameraProjection(osg::Camera *camera, int width, int height, int oldWidth, int oldHeight);


ViewportResizeHandler::ViewportResizeHandler(int zoneSize)
: osgGA::GUIEventHandler()
, _graphicsWindow()
, _zoneSize(zoneSize)
, _moveKey(osgGA::GUIEventAdapter::KEY_Control_L)
, _minWidth(2*zoneSize)
, _minHeight(2*zoneSize)
, _currentZone(None)
, _moveEnabled(false)
, _offsetX(0)
, _offsetY(0)
{
}

ViewportResizeHandler::ViewportResizeHandler(const ViewportResizeHandler& orig, const osg::CopyOp& copyop )
: osgGA::GUIEventHandler(orig,copyop)
, _graphicsWindow(orig._graphicsWindow)
, _zoneSize(orig._zoneSize)
, _moveKey(orig._moveKey)
, _currentZone(None)
, _moveEnabled(false)
, _offsetX(0)
, _offsetY(0)
{
}

void ViewportResizeHandler::setZoneSize(int zoneSize)
{
  _zoneSize = std::max(1,zoneSize);
  _minWidth  = std::max(_minWidth, 2*_zoneSize);
  _minHeight = std::max(_minHeight, 2*_zoneSize);
}

void ViewportResizeHandler::setMinSize(int minWidth, int minHeight)
{
  _minWidth  = std::max(minWidth, 2*_zoneSize);
  _minHeight = std::max(minHeight, 2*_zoneSize);
}

bool ViewportResizeHandler::handle(const osgGA::GUIEventAdapter& ea,
                                   osgGA::GUIActionAdapter& aa )
{
  osg::Camera * camera = aa.asView()->getCamera();
  if ( !camera )
    return false;

  bool handled = true;
  Zone zone = None;

  osg::Viewport *vp = camera->getViewport();
  bool inViewport = in_range<int>(ea.getX(), vp->x(), vp->x()+vp->width()) &&
                    in_range<int>(ea.getY(), vp->y(), vp->y()+vp->height());

  switch ( ea.getEventType() )
  {
    case osgGA::GUIEventAdapter::KEYDOWN:
      _moveEnabled = inViewport && (ea.getKey() == _moveKey);
      _currentZone = None;
      handled = ea.getKey() == _moveKey;
      break;

    case osgGA::GUIEventAdapter::KEYUP:
      _moveEnabled = _moveEnabled && (ea.getKey() != _moveKey);
      zone = inViewport ? getZone(ea, vp, _zoneSize) : None;
      handled = ea.getKey() == _moveKey;
      break;

    case osgGA::GUIEventAdapter::MOVE:
      if ( !inViewport )
      {
        _moveEnabled = false;
      }
      else if ( !_moveEnabled )
      {
        zone = getZone(ea, vp, _zoneSize);
      }
      handled = _moveEnabled || zone != None;
      break;

    case osgGA::GUIEventAdapter::PUSH:
      if ( inViewport )
      {
        if ( _moveEnabled )
        {
          _offsetX = ea.getX() - vp->x();
          _offsetY = ea.getY() - vp->y();
          _currentZone = None;
        }
        else
        {
          zone = _currentZone = getZone(ea, vp, _zoneSize);
          if ( _currentZone != None )
          {
            getOffsets(ea.getX(), ea.getY(), vp, _currentZone, _offsetX, _offsetY);
          }
        }
        handled = _moveEnabled || zone != None;
      } // in viewport
      else
      {
        _currentZone = None;
        handled = false;
      }
      break;

    case osgGA::GUIEventAdapter::DRAG:
      // Note: Don't check if in the viewport here because while dragging
      // we may have moved out of the viewport before it is updated
      if ( _moveEnabled )
      {
        // clamp to edges minus some margin so can't totally move out of the window
        int x = clamp<int>(ea.getX()-_offsetX, _zoneSize-vp->width(), ea.getGraphicsContext()->getTraits()->width-_zoneSize);
        int y = clamp<int>(ea.getY()-_offsetY, _zoneSize-vp->height(), ea.getGraphicsContext()->getTraits()->height-_zoneSize);
        camera->setViewport(x, y, vp->width(), vp->height());
      }
      else if ( _currentZone != None )
      {
        handleResize(ea, camera, vp, _currentZone, _offsetX, _offsetY, _minWidth, _minHeight);
      }
      handled = _moveEnabled || _currentZone != None;
      break;

    case osgGA::GUIEventAdapter::RELEASE:
      if ( !_moveEnabled )
      {
        zone = _currentZone = getZone(ea, vp, _zoneSize);
      }
      handled = _moveEnabled || zone != None;
      break;

    default:
      handled = false;
      break;
  } // event type switch

  if ( inViewport)
  {
    // NOTE: Not sure what the best way is to handle the graphics window, but this works for now.
    if ( !_graphicsWindow )
    {
       _graphicsWindow = dynamic_cast<osgViewer::GraphicsWindow *>(const_cast<osg::GraphicsContext *>(ea.getGraphicsContext()));
    }
    if ( _graphicsWindow )
    {
      osgViewer::GraphicsWindow::MouseCursor cursor = _moveEnabled ? osgViewer::GraphicsWindow::HandCursor : getZoneCursor(zone);
      _graphicsWindow->setCursor(cursor);
    }
  }

  return handled;
} // ViewportResizeHandler::handle

/**
 *  Determine the zone that the mouse occupies within the viewport.
 */
ViewportResizeHandler::Zone getZone(const osgGA::GUIEventAdapter& ea, const osg::Viewport *vp, int zoneSize)
{
  int mx = ea.getX();
  int my = ea.getY();
  int vpx0 = vp->x();
  int vpx1 = vp->x() + vp->width();
  int vpy0 = vp->y();
  int vpy1 = vp->y() + vp->height();

  if ( in_range<int>(mx,vpx0,vpx0+zoneSize) && in_range<int>(my,vpy0,vpy0+zoneSize) )
  {
    return ViewportResizeHandler::BottomLeft;
  }
  else if ( in_range<int>(mx,vpx1-zoneSize,vpx1) && in_range<int>(my,vpy0,vpy0+zoneSize) )
  {
    return ViewportResizeHandler::BottomRight;
  }
  else if ( in_range<int>(mx,vpx0,vpx0+zoneSize) && in_range<int>(my,vpy1-zoneSize,vpy1) )
  {
    return ViewportResizeHandler::TopLeft;
  }
  else if ( in_range<int>(mx,vpx1-zoneSize,vpx1) && in_range<int>(my,vpy1-zoneSize,vpy1) )
  {
    return ViewportResizeHandler::TopRight;
  }
  else if ( in_range<int>(mx,vpx0,vpx0+zoneSize) )
  {
    return ViewportResizeHandler::Left;
  }
  else if ( in_range<int>(mx,vpx1-zoneSize,vpx1) )
  {
    return ViewportResizeHandler::Right;
  }
  else if ( in_range<int>(my,vpy0,vpy0+zoneSize) )
  {
    return ViewportResizeHandler::Bottom;
  }
  else if ( in_range<int>(my,vpy1-zoneSize,vpy1) )
  {
    return ViewportResizeHandler::Top;
  }
  return ViewportResizeHandler::None;
} // ViewportResizeHandler::getZone

/**
 *  Determine the distance from the mouse position to the zone edge when the mouse button is pushed.
 *  This will be used as the offset for subsequence drag operations to set the actual edge position.
 */
void getOffsets(int mx, int my, const osg::Viewport *vp,
                ViewportResizeHandler::Zone zone, int& offsetX, int& offsetY)
{
  // Use mouse coordinates within the viewport
  mx -= vp->x();
  my -= vp->y();

  switch ( zone )
  {
    case ViewportResizeHandler::Left:
      offsetX = mx;
      offsetY = 0;
      break;
    case ViewportResizeHandler::Right:
      offsetX = mx - vp->width();
      offsetY = 0;
      break;
    case ViewportResizeHandler::Bottom:
      offsetX = 0;
      offsetY = my;
      break;
    case ViewportResizeHandler::Top:
      offsetX = 0;
      offsetY = my - vp->height();
      break;
    case ViewportResizeHandler::BottomLeft:
      offsetX = mx;
      offsetY = my;
      break;
    case ViewportResizeHandler::BottomRight:
      offsetX = mx - vp->width();
      offsetY = my;
      break;
    case ViewportResizeHandler::TopLeft:
      offsetX = mx;
      offsetY = my - vp->height();
      break;
    case ViewportResizeHandler::TopRight:
      offsetX = mx - vp->width();
      offsetY = my - vp->height();
      break;
    default:
      offsetX = 0;
      offsetY = 0;
      break;
  }
} // getOffsets

void handleResize(const osgGA::GUIEventAdapter& ea, osg::Camera *camera, const osg::Viewport *vp, ViewportResizeHandler::Zone currentZone, int offsetX, int offsetY, int minWidth, int minHeight)
{
  int windowWidth  = ea.getGraphicsContext()->getTraits()->width;
  int windowHeight = ea.getGraphicsContext()->getTraits()->height;
  int vpWidthOld   = vp->width();
  int vpWidthNew   = vpWidthOld;
  int vpHeightOld  = vp->height();
  int vpHeightNew  = vpHeightOld;
  int x = ea.getX() - offsetX;
  int y = ea.getY() - offsetY;

  switch ( currentZone )
  {
    case ViewportResizeHandler::Left:
    {
      x = clamp<int>( x, 0, vp->x()+vp->width()-minWidth );
      int dx = vp->x() - x;
      vpWidthNew = vp->width() + dx;
      camera->setViewport( x, vp->y(), vpWidthNew, vp->height() );
      break;
    }
    case ViewportResizeHandler::Right:
    {
      x = clamp<int>( x, vp->x()+minWidth, windowWidth );
      vpWidthNew = x - vp->x();
      camera->setViewport( vp->x(), vp->y(), vpWidthNew, vp->height() );
      break;
    }
    case ViewportResizeHandler::Bottom:
    {
      y = clamp<int>( y, 0, vp->y()+vp->height()-minHeight );
      int dy = vp->y() - y;
      vpHeightNew = vp->height() + dy;
      camera->setViewport( vp->x(), y, vp->width(), vpHeightNew );
      break;
    }
    case ViewportResizeHandler::Top:
    {
      y = clamp<int>( y, vp->y()+minHeight, windowHeight );
      vpHeightNew = y - vp->y();
      camera->setViewport( vp->x(), vp->y(), vp->width(), vpHeightNew );
      break;
    }
    case ViewportResizeHandler::BottomLeft:
    {
      x = clamp<int>( x, 0, vp->x()+vp->width()-minWidth );
      y = clamp<int>( y, 0, vp->y()+vp->height()-minHeight );
      int dx = vp->x() - x;
      int dy = vp->y() - y;
      vpWidthNew  = vp->width() + dx;
      vpHeightNew = vp->height() + dy;
      camera->setViewport( x, y, vpWidthNew, vpHeightNew );
      break;
    }
    case ViewportResizeHandler::BottomRight:
    {
      x = clamp<int>( x, vp->x()+minWidth, windowWidth );
      y = clamp<int>( y, 0, vp->y()+vp->height()-minHeight );
      int dy = vp->y() - y;
      vpWidthNew  = x - vp->x();
      vpHeightNew = vp->height() + dy;
      camera->setViewport( vp->x(), y, vpWidthNew, vpHeightNew );
      break;
    }
    case ViewportResizeHandler::TopLeft:
    {
      x = clamp<int>( x, 0, vp->x()+vp->width()-minWidth );
      y = clamp<int>( y, vp->y()+minHeight, windowHeight );
      int dx = vp->x() - x;
      vpWidthNew  = vp->width() + dx;
      vpHeightNew = y - vp->y();
      camera->setViewport( x, vp->y(), vpWidthNew, vpHeightNew );
      break;
    }
    case ViewportResizeHandler::TopRight:
    {
      x = clamp<int>( x, vp->x()+minWidth, windowWidth );
      y = clamp<int>( y, vp->y()+minHeight, windowHeight );
      vpWidthNew  = x - vp->x();
      vpHeightNew = y - vp->y();
      camera->setViewport( vp->x(), vp->y(), vpWidthNew, vpHeightNew );
      break;
    }
  } // zone switch

  updateCameraProjection(camera, vpWidthNew, vpHeightNew, vpWidthOld, vpHeightOld);
} // handleResize

osgViewer::GraphicsWindow::MouseCursor getZoneCursor( ViewportResizeHandler::Zone zone )
{
  static std::map<ViewportResizeHandler::Zone,osgViewer::GraphicsWindow::MouseCursor> cursors;
  if ( cursors.empty() ) {
    cursors[ViewportResizeHandler::Left]        = osgViewer::GraphicsWindow::LeftSideCursor;
    cursors[ViewportResizeHandler::Right]       = osgViewer::GraphicsWindow::RightSideCursor;
    cursors[ViewportResizeHandler::Bottom]      = osgViewer::GraphicsWindow::BottomSideCursor;
    cursors[ViewportResizeHandler::Top]         = osgViewer::GraphicsWindow::TopSideCursor;
    cursors[ViewportResizeHandler::BottomLeft]  = osgViewer::GraphicsWindow::BottomLeftCorner;
    cursors[ViewportResizeHandler::BottomRight] = osgViewer::GraphicsWindow::BottomRightCorner;
    cursors[ViewportResizeHandler::TopLeft]     = osgViewer::GraphicsWindow::TopLeftCorner;
    cursors[ViewportResizeHandler::TopRight]    = osgViewer::GraphicsWindow::TopRightCorner;
    cursors[ViewportResizeHandler::None]        = osgViewer::GraphicsWindow::InheritCursor;
  }

  return cursors[zone];
} // getZoneCursor

/**
 *  Update the Camera projection based on the adjusted viewport size/aspect ratios.
 *  This is based on osg::GraphicsContext::resizedImplementation()
 *  TODO: this should probably go in the Camera implementation or someplace else
 */
void updateCameraProjection(osg::Camera *camera,
                            int width, int height,
                            int oldWidth, int oldHeight)
{
  // resize doesn't affect Cameras set up with FBO's.
  if (camera->getRenderTargetImplementation()==osg::Camera::FRAME_BUFFER_OBJECT) return;

  double widthChangeRatio = double(width) / oldWidth; // double(_traits->width);
  double heigtChangeRatio = double(height) / oldHeight; // double(_traits->height);
  double aspectRatioChange = widthChangeRatio / heigtChangeRatio;

  // if aspect ratio adjusted change the project matrix to suit.
  if (aspectRatioChange != 1.0)
  {
    osg::View* view = camera->getView();
    osg::View::Slave* slave = view ? view->findSlaveForCamera(camera) : 0;

    if (slave)
    {
      if (camera->getReferenceFrame()==osg::Transform::RELATIVE_RF)
      {
        switch(view->getCamera()->getProjectionResizePolicy()) {
          case(osg::Camera::HORIZONTAL): slave->_projectionOffset *= osg::Matrix::scale(1.0/aspectRatioChange,1.0,1.0); break;
          case(osg::Camera::VERTICAL): slave->_projectionOffset *= osg::Matrix::scale(1.0, aspectRatioChange,1.0); break;
          default: break;
        }
      }
      else
      {
        switch(camera->getProjectionResizePolicy())
        {
          case(osg::Camera::HORIZONTAL): camera->getProjectionMatrix() *= osg::Matrix::scale(1.0/aspectRatioChange,1.0,1.0); break;
          case(osg::Camera::VERTICAL): camera->getProjectionMatrix() *= osg::Matrix::scale(1.0, aspectRatioChange,1.0); break;
          default: break;
        }
      }
    }
    else
    {
      osg::Camera::ProjectionResizePolicy policy = view ? view->getCamera()->getProjectionResizePolicy() : camera->getProjectionResizePolicy();
      switch(policy)
      {
        case(osg::Camera::HORIZONTAL): camera->getProjectionMatrix() *= osg::Matrix::scale(1.0/aspectRatioChange,1.0,1.0); break;
        case(osg::Camera::VERTICAL): camera->getProjectionMatrix() *= osg::Matrix::scale(1.0, aspectRatioChange,1.0); break;
        default: break;
      }

      osg::Camera* master = view ? view->getCamera() : 0;
      if (view && camera==master)
      {
        for(unsigned int i=0; i<view->getNumSlaves(); ++i)
        {
          osg::View::Slave& child = view->getSlave(i);
          if (child._camera.valid() && child._camera->getReferenceFrame()==osg::Transform::RELATIVE_RF)
          {
            // scale the slaves by the inverse of the change that has been applied to master, to avoid them be
            // scaled twice (such as when both master and slave are on the same GraphicsContexts) or by the wrong scale
            // when master and slave are on different GraphicsContexts.
            switch(policy)
            {
              case(osg::Camera::HORIZONTAL): child._projectionOffset *= osg::Matrix::scale(aspectRatioChange,1.0,1.0); break;
              case(osg::Camera::VERTICAL): child._projectionOffset *= osg::Matrix::scale(1.0, 1.0/aspectRatioChange,1.0); break;
              default: break;
            }
          }
        }
      }
    }
  }
} // updateCameraProjection
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to