Question regarding osgEarth::Utils::EarthManipulator and osgEarth::Utils::EarthManipulator::Settings

classic Classic list List threaded Threaded
2 messages Options
Blanky Blanky
Reply | Threaded
Open this post in threaded view
|

Question regarding osgEarth::Utils::EarthManipulator and osgEarth::Utils::EarthManipulator::Settings

Hey,

So when I pan the world I'd like to be able to clamp the absolute farthest distance the viewpoint that the camera contains to be the edge of the extents of the osgEarth::MapNode in a projected map. Currently my "hacky" solution is the following:

        osgEarth::Viewpoint vp = _manip->getViewpoint();
        osgEarth::GeoPoint focalPoint = *vp.focalPoint();
        if(focalPoint.y() + (vp.range()->as(osgEarth::Units::METERS)/2) > _mapExtent.yMax()){
            focalPoint.y() = _mapExtent.yMax() - (vp.range()->as(osgEarth::Units::METERS)/2);
            vp.focalPoint() = focalPoint;
            _manip->setViewpoint(vp);
        }else if(focalPoint.y() - (vp.range()->as(osgEarth::Units::METERS)/2) < _mapExtent.yMin()){
            focalPoint.y() = _mapExtent.yMin() + (vp.range()->as(osgEarth::Units::METERS)/2);
            vp.focalPoint() = focalPoint;
            _manip->setViewpoint(vp);
        }


Now, this works, but seems to cause a jittery effect instead of a hard clamping effect when continuously dragging/panning the manipulator toward the edges. This makes sense since this code is reactionary rather than proactively checking if it's valid before setting it. I looked into the EarthManipulator's settings and saw the function:

EarthManipulator::Settings::setMaxOffset(double max_x_offset, double max_y_offset)

This is exactly what I want, but this only works when you're tethered to a node. Now, I can get around this by making an osgEarth::GeoPoint and positioning it at Lat: 0, Lon: 0, and base the yOffset from there. However, depending on the range that the camera manipulator is at, this will be like a bandaid fix. I simply just don't want the user to see the empty space behind the osgEarth::MapNode that the osg::Camera's clear color makes without a jittery reactive solution.

For example, this works great for setting the max and min pitch level without getting a jittery effect, I just want the same for when I'm not tethered to a node and for panning.

Since the camera can have a different focal point at different ranges, then if I was zoomed in close to the edge but zoomed out, then my focal point would have to shift the y value down accordingly. I essentially want the manipulator's viewpoint to only be valid when fully containing a portion of the osgEarth::MapNode's bounding rectangle.

Thanks for any help.

- Blanky
Blanky Blanky
Reply | Threaded
Open this post in threaded view
|

Re: Question regarding osgEarth::Utils::EarthManipulator and osgEarth::Utils::EarthManipulator::Settings

Well, so seeing as I just managed a sort of solution - although not the most efficient maybe? I thought maybe someone else may happen to need this. A jitterless solution that manages to work generically when setting the viewpoint or panning is done requires the following to be done:

1. Subclass osgEarth::Util::EarthManipulator
2. Add the extent to be used by the projected map as a member variable and construct your manipulator with said extent. The extent can be grabbed like so:

_mapNode->getMap()->getProfile()->getExtent()

3. Reimplement the following virtual functions within the class:

    class CustomManipulator : public osgEarth::Util::EarthManipulator{
    public:
        CustomManipulator (osgEarth::GeoExtent extent);
        virtual void setViewpoint(const osgEarth::Viewpoint& vp, double durationSeconds = 0.0);
        virtual void pan(double dx, double dy);
   
    private:
        osgEarth::GeoExtent m_mapExtent;
    };

4. Make these the contents of the code:

*PS: Look for keyword NEW START for the beginning of the changes within both functions and NEW END for when the rest of the code continues. None of the original code was removed/changed other than between these comment areas. Some of the anonymous namespace functions are necessary to get this to work. Copying and pasting them sufficed for me and everything seems to be working rather well.

void CustomManipulator::setViewpoint(const osgEarth::Viewpoint& vp, double durationSeconds){
    // If the manip is not set up, save the viewpoint for later.
       if ( !established() )
       {
           _pendingViewpoint = vp;
           _pendingViewpointDuration.set(durationSeconds, osgEarth::Units::SECONDS);
       }

       else
       {
           // Save any existing tether node so we can properly invoke the callback.
           osg::ref_ptr<osg::Node> oldEndNode;
           if ( isTethering() && _tetherCallback.valid() )
               _setVP1->getNode(oldEndNode);

           // starting viewpoint; all fields will be set:
           _setVP0 = getViewpoint();

           // ending viewpoint
           _setVP1 = vp;

           //NEW START ------ Fixes going out of the map's extents -------- NEW START
           //Sanitation check on ending vp to be fully within y extent of the map
           if(!isTethering()){
               osgEarth::optional<osgEarth::Viewpoint> sanitizedVP(_setVP1);
               osgEarth::GeoPoint sanitizedGeoPoint;

               double newDistance = vp.range()->as(osgEarth::Units::METERS);

               double scale = -0.3f * newDistance;

               osg::Matrixd rotation_matrix;
               rotation_matrix.makeRotate( _rotation * _centerRotation  );
               // compute look vector.
               osg::Vec3d sideVector = getSideVector(rotation_matrix);

               osg::Vec3d localUp = _previousUp;

               osg::Vec3d forwardVector =localUp^sideVector;
               sideVector = forwardVector^localUp;

               forwardVector.normalize();
               sideVector.normalize();

               osg::Vec3d dv = forwardVector * (0.00001 * scale) + sideVector * (0.00001 * scale);

               // save the previous CF so we can do azimuth locking:
               osg::CoordinateFrame oldCenterLocalToWorld = _centerLocalToWorld;

               // move the center point
               osg::Vec3d newCenter = _center + dv;

               //This actually fixes up the world coordinate to be correct when zooming
               while(newCenter.y() + 0.3f * newDistance > m_mapExtent.yMax()){
                   newCenter.y() -= 1.0;
               }
               while(newCenter.y() - 0.3f * newDistance < m_mapExtent.yMin()){
                   newCenter.y() += 1.0;
               }

               sanitizedGeoPoint.fromWorld(_srs, newCenter);
               sanitizedVP->focalPoint() = sanitizedGeoPoint;
               _setVP1 = sanitizedVP;
           }
           //NEW END ------ Fixes going out of the map's extents -------- NEW END

           
           // Reset the tethering offset quat.
           _tetherRotationVP0 = _tetherRotation;
           _tetherRotationVP1 = osg::Quat();

           ... //Rest of the code for setViewpoint
       }

    void CustomManipulator::pan(double dx, double dy){
        if ( !isTethering() )
        {
            // to pan, we need a focus point on the terrain:
            if ( !recalculateCenterFromLookVector() ){
                return;
            }

            double scale = -0.3f*_distance;

            osg::Matrixd rotation_matrix;
            rotation_matrix.makeRotate( _rotation * _centerRotation  );

            // compute look vector.
            osg::Vec3d sideVector = getSideVector(rotation_matrix);

            osg::Vec3d localUp = _previousUp;

            osg::Vec3d forwardVector =localUp^sideVector;
            sideVector = forwardVector^localUp;

            forwardVector.normalize();
            sideVector.normalize();

            osg::Vec3d dv = forwardVector * (dy*scale) + sideVector * (dx*scale);

            // save the previous CF so we can do azimuth locking:
            osg::CoordinateFrame oldCenterLocalToWorld = _centerLocalToWorld;

            // move the center point
            double len = _center.length();
            osg::Vec3d newCenter = _center + dv;

            //NEW START ------ Fixes going out of the map's extents -------- NEW START
            //Fixes going out of the map's extents
            while(newCenter.y() + 0.3f * _distance > m_mapExtent.yMax()){
                newCenter.y() -= 1.0;
            }

            while(newCenter.y() - 0.3f * _distance < m_mapExtent.yMin()){
                newCenter.y() += 1.0;
            }
            //NEW END ------ Fixes going out of the map's extents -------- NEW END

        ... //Rest of pan code below
    }


If there's a better/easier way to do this without tethering, please let me know, but if this is a half decent solution that doesn't cause any issues, then we're probably going to end up using this for now.

- Blanky