1 /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2010 Robert Osfield
2  *
3  * This library is open source and may be redistributed and/or modified under
4  * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5  * (at your option) any later version.  The full license is in LICENSE file
6  * included with this distribution, and on the openscenegraph.org website.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * OpenSceneGraph Public License for more details.
12  *
13  * OrbitManipulator code Copyright (C) 2010 PCJohn (Jan Peciva)
14  * while some pieces of code were taken from OSG.
15  * Thanks to company Cadwork (www.cadwork.ch) and
16  * Brno University of Technology (www.fit.vutbr.cz) for open-sourcing this work.
17 */
18 
19 #include <osgGA/OrbitManipulator>
20 #include <osg/BoundsChecking>
21 #include <cassert>
22 
23 using namespace osg;
24 using namespace osgGA;
25 
26 
27 
28 int OrbitManipulator::_minimumDistanceFlagIndex = allocateRelativeFlag();
29 
30 
31 /// Constructor.
OrbitManipulator(int flags)32 OrbitManipulator::OrbitManipulator( int flags )
33    : inherited( flags ),
34      _distance( 1. ),
35      _trackballSize( 0.8 )
36 {
37     setMinimumDistance( 0.05, true );
38     setWheelZoomFactor( 0.1 );
39     if( _flags & SET_CENTER_ON_WHEEL_FORWARD_MOVEMENT )
40         setAnimationTime( 0.2 );
41 }
42 
43 
44 /// Constructor.
OrbitManipulator(const OrbitManipulator & om,const CopyOp & copyOp)45 OrbitManipulator::OrbitManipulator( const OrbitManipulator& om, const CopyOp& copyOp )
46     : osg::Object(om, copyOp),
47      osg::Callback(om, copyOp),
48      inherited( om, copyOp ),
49      _center( om._center ),
50      _rotation( om._rotation ),
51      _distance( om._distance ),
52      _trackballSize( om._trackballSize ),
53      _wheelZoomFactor( om._wheelZoomFactor ),
54      _minimumDistance( om._minimumDistance )
55 {
56 }
57 
58 
59 /** Set the position of the manipulator using a 4x4 matrix.*/
setByMatrix(const osg::Matrixd & matrix)60 void OrbitManipulator::setByMatrix( const osg::Matrixd& matrix )
61 {
62     _center = osg::Vec3d( 0., 0., -_distance ) * matrix;
63     _rotation = matrix.getRotate();
64 
65     // fix current rotation
66     if( getVerticalAxisFixed() )
67         fixVerticalAxis( _center, _rotation, true );
68 }
69 
70 
71 /** Set the position of the manipulator using a 4x4 matrix.*/
setByInverseMatrix(const osg::Matrixd & matrix)72 void OrbitManipulator::setByInverseMatrix( const osg::Matrixd& matrix )
73 {
74     setByMatrix( osg::Matrixd::inverse( matrix ) );
75 }
76 
77 
78 /** Get the position of the manipulator as 4x4 matrix.*/
getMatrix() const79 osg::Matrixd OrbitManipulator::getMatrix() const
80 {
81     return osg::Matrixd::translate( 0., 0., _distance ) *
82            osg::Matrixd::rotate( _rotation ) *
83            osg::Matrixd::translate( _center );
84 }
85 
86 
87 /** Get the position of the manipulator as a inverse matrix of the manipulator,
88     typically used as a model view matrix.*/
getInverseMatrix() const89 osg::Matrixd OrbitManipulator::getInverseMatrix() const
90 {
91     return osg::Matrixd::translate( -_center ) *
92            osg::Matrixd::rotate( _rotation.inverse() ) *
93            osg::Matrixd::translate( 0.0, 0.0, -_distance );
94 }
95 
96 
97 // doc in parent
setTransformation(const osg::Vec3d & eye,const osg::Quat & rotation)98 void OrbitManipulator::setTransformation( const osg::Vec3d& eye, const osg::Quat& rotation )
99 {
100     _center = eye + rotation * osg::Vec3d( 0., 0., -_distance );
101     _rotation = rotation;
102 
103     // fix current rotation
104     if( getVerticalAxisFixed() )
105         fixVerticalAxis( _center, _rotation, true );
106 }
107 
108 
109 // doc in parent
getTransformation(osg::Vec3d & eye,osg::Quat & rotation) const110 void OrbitManipulator::getTransformation( osg::Vec3d& eye, osg::Quat& rotation ) const
111 {
112     eye = _center - _rotation * osg::Vec3d( 0., 0., -_distance );
113     rotation = _rotation;
114 }
115 
116 
117 // doc in parent
setTransformation(const osg::Vec3d & eye,const osg::Vec3d & center,const osg::Vec3d & up)118 void OrbitManipulator::setTransformation( const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up )
119 {
120     Vec3d lv( center - eye );
121 
122     Vec3d f( lv );
123     f.normalize();
124     Vec3d s( f^up );
125     s.normalize();
126     Vec3d u( s^f );
127     u.normalize();
128 
129     osg::Matrixd rotation_matrix( s[0], u[0], -f[0], 0.0f,
130                             s[1], u[1], -f[1], 0.0f,
131                             s[2], u[2], -f[2], 0.0f,
132                             0.0f, 0.0f,  0.0f, 1.0f );
133 
134     _center = center;
135     _distance = lv.length();
136     _rotation = rotation_matrix.getRotate().inverse();
137 
138     // fix current rotation
139     if( getVerticalAxisFixed() )
140         fixVerticalAxis( _center, _rotation, true );
141 }
142 
143 
144 // doc in parent
getTransformation(osg::Vec3d & eye,osg::Vec3d & center,osg::Vec3d & up) const145 void OrbitManipulator::getTransformation( osg::Vec3d& eye, osg::Vec3d& center, osg::Vec3d& up ) const
146 {
147     center = _center;
148     eye = _center + _rotation * osg::Vec3d( 0., 0., _distance );
149     up = _rotation * osg::Vec3d( 0., 1., 0. );
150 }
151 
152 
153 /** Sets the transformation by heading. Heading is given as an angle in radians giving a azimuth in xy plane.
154     Its meaning is similar to longitude used in cartography and navigation.
155     Positive number is going to the east direction.*/
setHeading(double azimuth)156 void OrbitManipulator::setHeading( double azimuth )
157 {
158     CoordinateFrame coordinateFrame = getCoordinateFrame( _center );
159     Vec3d localUp = getUpVector( coordinateFrame );
160     Vec3d localRight = getSideVector( coordinateFrame );
161 
162     Vec3d dir = Quat( getElevation(), localRight ) * Quat( azimuth, localUp ) * Vec3d( 0., -_distance, 0. );
163 
164     setTransformation( _center + dir, _center, localUp );
165 }
166 
167 
168 /// Returns the heading in radians. \sa setHeading
getHeading() const169 double OrbitManipulator::getHeading() const
170 {
171     CoordinateFrame coordinateFrame = getCoordinateFrame( _center );
172     Vec3d localFront = getFrontVector( coordinateFrame );
173     Vec3d localRight = getSideVector( coordinateFrame );
174 
175     Vec3d center, eye, tmp;
176     getTransformation( eye, center, tmp );
177 
178     Plane frontPlane( localFront, center );
179     double frontDist = frontPlane.distance( eye );
180     Plane rightPlane( localRight, center );
181     double rightDist = rightPlane.distance( eye );
182 
183     return atan2( rightDist, -frontDist );
184 }
185 
186 
187 /** Sets the transformation by elevation. Elevation is given as an angle in radians from xy plane.
188     Its meaning is similar to latitude used in cartography and navigation.
189     Positive number is going to the north direction, negative to the south.*/
setElevation(double elevation)190 void OrbitManipulator::setElevation( double elevation )
191 {
192     CoordinateFrame coordinateFrame = getCoordinateFrame( _center );
193     Vec3d localUp = getUpVector( coordinateFrame );
194     Vec3d localRight = getSideVector( coordinateFrame );
195 
196     Vec3d dir = Quat( -elevation, localRight ) * Quat( getHeading(), localUp ) * Vec3d( 0., -_distance, 0. );
197 
198     setTransformation( _center + dir, _center, localUp );
199 }
200 
201 
202 /// Returns the elevation in radians. \sa setElevation
getElevation() const203 double OrbitManipulator::getElevation() const
204 {
205     CoordinateFrame coordinateFrame = getCoordinateFrame( _center );
206     Vec3d localUp = getUpVector( coordinateFrame );
207     localUp.normalize();
208 
209     Vec3d center, eye, tmp;
210     getTransformation( eye, center, tmp );
211 
212     Plane plane( localUp, center );
213     double dist = plane.distance( eye );
214 
215     return asin( -dist / (eye-center).length() );
216 }
217 
218 
219 // doc in parent
handleMouseWheel(const GUIEventAdapter & ea,GUIActionAdapter & us)220 bool OrbitManipulator::handleMouseWheel( const GUIEventAdapter& ea, GUIActionAdapter& us )
221 {
222     osgGA::GUIEventAdapter::ScrollingMotion sm = ea.getScrollingMotion();
223 
224     // handle centering
225     if( _flags & SET_CENTER_ON_WHEEL_FORWARD_MOVEMENT )
226     {
227 
228         if( ((sm == GUIEventAdapter::SCROLL_DOWN && _wheelZoomFactor > 0.)) ||
229             ((sm == GUIEventAdapter::SCROLL_UP   && _wheelZoomFactor < 0.)) )
230         {
231 
232             if( getAnimationTime() <= 0. )
233             {
234                 // center by mouse intersection (no animation)
235                 setCenterByMousePointerIntersection( ea, us );
236             }
237             else
238             {
239                 // start new animation only if there is no animation in progress
240                 if( !isAnimating() )
241                     startAnimationByMousePointerIntersection( ea, us );
242 
243             }
244 
245         }
246     }
247 
248     switch( sm )
249     {
250         // mouse scroll up event
251         case GUIEventAdapter::SCROLL_UP:
252         {
253             // perform zoom
254             zoomModel( _wheelZoomFactor, true );
255             us.requestRedraw();
256             us.requestContinuousUpdate( isAnimating() || _thrown );
257             return true;
258         }
259 
260         // mouse scroll down event
261         case GUIEventAdapter::SCROLL_DOWN:
262         {
263             // perform zoom
264             zoomModel( -_wheelZoomFactor, true );
265             us.requestRedraw();
266             us.requestContinuousUpdate( isAnimating() || _thrown );
267             return true;
268         }
269 
270         // unhandled mouse scrolling motion
271         default:
272             return false;
273    }
274 }
275 
276 
277 // doc in parent
performMovementLeftMouseButton(const double eventTimeDelta,const double dx,const double dy)278 bool OrbitManipulator::performMovementLeftMouseButton( const double eventTimeDelta, const double dx, const double dy )
279 {
280     // rotate camera
281     if( getVerticalAxisFixed() )
282         rotateWithFixedVertical( dx, dy );
283     else
284         rotateTrackball( _ga_t0->getXnormalized(), _ga_t0->getYnormalized(),
285                          _ga_t1->getXnormalized(), _ga_t1->getYnormalized(),
286                          getThrowScale( eventTimeDelta ) );
287     return true;
288 }
289 
290 
291 // doc in parent
performMovementMiddleMouseButton(const double eventTimeDelta,const double dx,const double dy)292 bool OrbitManipulator::performMovementMiddleMouseButton( const double eventTimeDelta, const double dx, const double dy )
293 {
294     // pan model
295     float scale = -0.3f * _distance * getThrowScale( eventTimeDelta );
296     panModel( dx*scale, dy*scale );
297     return true;
298 }
299 
300 
301 // doc in parent
performMovementRightMouseButton(const double eventTimeDelta,const double,const double dy)302 bool OrbitManipulator::performMovementRightMouseButton( const double eventTimeDelta, const double /*dx*/, const double dy )
303 {
304     // zoom model
305     zoomModel( dy * getThrowScale( eventTimeDelta ), true );
306     return true;
307 }
308 
309 
performMouseDeltaMovement(const float dx,const float dy)310 bool OrbitManipulator::performMouseDeltaMovement( const float dx, const float dy )
311 {
312     // rotate camera
313     if( getVerticalAxisFixed() )
314         rotateWithFixedVertical( dx, dy );
315     else
316         rotateTrackball( 0.f, 0.f, dx, dy, 1.f );
317 
318     return true;
319 }
320 
321 
applyAnimationStep(const double currentProgress,const double prevProgress)322 void OrbitManipulator::applyAnimationStep( const double currentProgress, const double prevProgress )
323 {
324     OrbitAnimationData *ad = dynamic_cast< OrbitAnimationData* >( _animationData.get() );
325     assert( ad );
326 
327     // compute new center
328     osg::Vec3d prevCenter, prevEye, prevUp;
329     getTransformation( prevEye, prevCenter, prevUp );
330     osg::Vec3d newCenter = osg::Vec3d(prevCenter) + (ad->_movement * (currentProgress - prevProgress));
331 
332     // fix vertical axis
333     if( getVerticalAxisFixed() )
334     {
335 
336         CoordinateFrame coordinateFrame = getCoordinateFrame( newCenter );
337         Vec3d localUp = getUpVector( coordinateFrame );
338 
339         fixVerticalAxis( newCenter - prevEye, prevUp, prevUp, localUp, false );
340    }
341 
342    // apply new transformation
343    setTransformation( prevEye, newCenter, prevUp );
344 }
345 
346 
startAnimationByMousePointerIntersection(const osgGA::GUIEventAdapter & ea,osgGA::GUIActionAdapter & us)347 bool OrbitManipulator::startAnimationByMousePointerIntersection(
348       const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us )
349 {
350     // get current transformation
351     osg::Vec3d prevCenter, prevEye, prevUp;
352     getTransformation( prevEye, prevCenter, prevUp );
353 
354     // center by mouse intersection
355     if( !setCenterByMousePointerIntersection( ea, us ) )
356         return false;
357 
358     OrbitAnimationData *ad = dynamic_cast< OrbitAnimationData*>( _animationData.get() );
359     if (!ad) return false;
360 
361     // setup animation data and restore original transformation
362     ad->start( osg::Vec3d(_center) - prevCenter, ea.getTime() );
363     setTransformation( prevEye, prevCenter, prevUp );
364 
365     return true;
366 }
367 
368 
start(const osg::Vec3d & movement,const double startTime)369 void OrbitManipulator::OrbitAnimationData::start( const osg::Vec3d& movement, const double startTime )
370 {
371     AnimationData::start( startTime );
372 
373     _movement = movement;
374 }
375 
376 
377 /** Performs trackball rotation based on two points given, for example,
378     by mouse pointer on the screen.
379 
380     Scale parameter is useful, for example, when manipulator is thrown.
381     It scales the amount of rotation based, for example, on the current frame time.*/
rotateTrackball(const float px0,const float py0,const float px1,const float py1,const float scale)382 void OrbitManipulator::rotateTrackball( const float px0, const float py0,
383                                         const float px1, const float py1, const float scale )
384 {
385     osg::Vec3d axis;
386     float angle;
387 
388     trackball( axis, angle, px0 + (px1-px0)*scale, py0 + (py1-py0)*scale, px0, py0 );
389 
390     Quat new_rotate;
391     new_rotate.makeRotate( angle, axis );
392 
393     _rotation = _rotation * new_rotate;
394 }
395 
396 
397 /** Performs rotation horizontally by dx parameter and vertically by dy parameter,
398     while keeping UP vector.*/
rotateWithFixedVertical(const float dx,const float dy)399 void OrbitManipulator::rotateWithFixedVertical( const float dx, const float dy )
400 {
401     CoordinateFrame coordinateFrame = getCoordinateFrame( _center );
402     Vec3d localUp = getUpVector( coordinateFrame );
403 
404     rotateYawPitch( _rotation, dx, dy, localUp );
405 }
406 
407 
408 /** Performs rotation horizontally by dx parameter and vertically by dy parameter,
409     while keeping UP vector given by up parameter.*/
rotateWithFixedVertical(const float dx,const float dy,const Vec3f & up)410 void OrbitManipulator::rotateWithFixedVertical( const float dx, const float dy, const Vec3f& up )
411 {
412     rotateYawPitch( _rotation, dx, dy, up );
413 }
414 
415 
416 /** Moves camera in x,y,z directions given in camera local coordinates.*/
panModel(const float dx,const float dy,const float dz)417 void OrbitManipulator::panModel( const float dx, const float dy, const float dz )
418 {
419     Matrix rotation_matrix;
420     rotation_matrix.makeRotate( _rotation );
421 
422     Vec3d dv( dx, dy, dz );
423 
424     _center += dv * rotation_matrix;
425 }
426 
427 
428 /** Changes the distance of camera to the focal center.
429     If pushForwardIfNeeded is true and minimumDistance is reached,
430     the focal center is moved forward. Otherwise, distance is limited
431     to its minimum value.
432     \sa OrbitManipulator::setMinimumDistance
433  */
zoomModel(const float dy,bool pushForwardIfNeeded)434 void OrbitManipulator::zoomModel( const float dy, bool pushForwardIfNeeded )
435 {
436     // scale
437     float scale = 1.0f + dy;
438 
439     // minimum distance
440     float minDist = _minimumDistance;
441     if( getRelativeFlag( _minimumDistanceFlagIndex ) )
442         minDist *= _modelSize;
443 
444     if( _distance*scale > minDist )
445     {
446         // regular zoom
447         _distance *= scale;
448     }
449     else
450     {
451         if( pushForwardIfNeeded )
452         {
453             // push the camera forward
454             float yscale = -_distance;
455             Matrixd rotation_matrix( _rotation );
456             Vec3d dv = (Vec3d( 0.0f, 0.0f, -1.0f ) * rotation_matrix) * (dy * yscale);
457             _center += dv;
458         }
459         else
460         {
461             // set distance on its minimum value
462             _distance = minDist;
463         }
464     }
465 }
466 
467 
468 /**
469  * Simulate a track-ball.  Project the points onto the virtual
470  * trackball, then figure out the axis of rotation, which is the cross
471  * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
472  * Note:  This is a deformed trackball-- is a trackball in the center,
473  * but is deformed into a hyperbolic sheet of rotation away from the
474  * center.  This particular function was chosen after trying out
475  * several variations.
476  *
477  * It is assumed that the arguments to this routine are in the range
478  * (-1.0 ... 1.0)
479  */
trackball(osg::Vec3d & axis,float & angle,float p1x,float p1y,float p2x,float p2y)480 void OrbitManipulator::trackball( osg::Vec3d& axis, float& angle, float p1x, float p1y, float p2x, float p2y )
481 {
482     /*
483         * First, figure out z-coordinates for projection of P1 and P2 to
484         * deformed sphere
485         */
486 
487     osg::Matrixd rotation_matrix(_rotation);
488 
489     osg::Vec3d uv = Vec3d(0.0f,1.0f,0.0f)*rotation_matrix;
490     osg::Vec3d sv = Vec3d(1.0f,0.0f,0.0f)*rotation_matrix;
491     osg::Vec3d lv = Vec3d(0.0f,0.0f,-1.0f)*rotation_matrix;
492 
493     osg::Vec3d p1 = sv * p1x + uv * p1y - lv * tb_project_to_sphere(_trackballSize, p1x, p1y);
494     osg::Vec3d p2 = sv * p2x + uv * p2y - lv * tb_project_to_sphere(_trackballSize, p2x, p2y);
495 
496     /*
497         *  Now, we want the cross product of P1 and P2
498         */
499     axis = p2^p1;
500     axis.normalize();
501 
502     /*
503         *  Figure out how much to rotate around that axis.
504         */
505     float t = (p2 - p1).length() / (2.0 * _trackballSize);
506 
507     /*
508         * Avoid problems with out-of-control values...
509         */
510     if (t > 1.0) t = 1.0;
511     if (t < -1.0) t = -1.0;
512     angle = inRadians(asin(t));
513 }
514 
515 
516 /**
517  * Helper trackball method that projects an x,y pair onto a sphere of radius r OR
518  * a hyperbolic sheet if we are away from the center of the sphere.
519  */
tb_project_to_sphere(float r,float x,float y)520 float OrbitManipulator::tb_project_to_sphere( float r, float x, float y )
521 {
522     float d, t, z;
523 
524     d = sqrt(x*x + y*y);
525                                  /* Inside sphere */
526     if (d < r * 0.70710678118654752440)
527     {
528         z = sqrt(r*r - d*d);
529     }                            /* On hyperbola */
530     else
531     {
532         t = r / 1.41421356237309504880;
533         z = t*t / d;
534     }
535     return z;
536 }
537 
538 
539 /** Get the FusionDistanceMode. Used by SceneView for setting up stereo convergence.*/
getFusionDistanceMode() const540 osgUtil::SceneView::FusionDistanceMode OrbitManipulator::getFusionDistanceMode() const
541 {
542     return osgUtil::SceneView::USE_FUSION_DISTANCE_VALUE;
543 }
544 
545 /** Get the FusionDistanceValue. Used by SceneView for setting up stereo convergence.*/
getFusionDistanceValue() const546 float OrbitManipulator::getFusionDistanceValue() const
547 {
548     return _distance;
549 }
550 
551 
552 /** Set the center of the manipulator. */
setCenter(const Vec3d & center)553 void OrbitManipulator::setCenter( const Vec3d& center )
554 {
555     _center = center;
556 }
557 
558 
559 /** Get the center of the manipulator. */
getCenter() const560 const Vec3d& OrbitManipulator::getCenter() const
561 {
562     return _center;
563 }
564 
565 
566 /** Set the rotation of the manipulator. */
setRotation(const Quat & rotation)567 void OrbitManipulator::setRotation( const Quat& rotation )
568 {
569     _rotation = rotation;
570 }
571 
572 
573 /** Get the rotation of the manipulator. */
getRotation() const574 const Quat& OrbitManipulator::getRotation() const
575 {
576     return _rotation;
577 }
578 
579 
580 /** Set the distance of camera to the center. */
setDistance(double distance)581 void OrbitManipulator::setDistance( double distance )
582 {
583     _distance = distance;
584 }
585 
586 
587 /** Get the distance of the camera to the center. */
getDistance() const588 double OrbitManipulator::getDistance() const
589 {
590     return _distance;
591 }
592 
593 
594 /** Set the size of the trackball. Value is relative to the model size. */
setTrackballSize(const double & size)595 void OrbitManipulator::setTrackballSize( const double& size )
596 {
597     /*
598     * This size should really be based on the distance from the center of
599     * rotation to the point on the object underneath the mouse.  That
600     * point would then track the mouse as closely as possible.  This is a
601     * simple example, though, so that is left as an Exercise for the
602     * Programmer.
603     */
604     _trackballSize = size;
605     clampBetweenRange( _trackballSize, 0.1, 1.0, "TrackballManipulator::setTrackballSize(float)" );
606 }
607 
608 
609 /** Set the mouse wheel zoom factor.
610     The amount of camera movement on each mouse wheel event
611     is computed as the current distance to the center multiplied by this factor.
612     For example, value of 0.1 will short distance to center by 10% on each wheel up event.
613     Use negative value for reverse mouse wheel direction.*/
setWheelZoomFactor(double wheelZoomFactor)614 void OrbitManipulator::setWheelZoomFactor( double wheelZoomFactor )
615 {
616     _wheelZoomFactor = wheelZoomFactor;
617 }
618 
619 
620 /** Set the minimum distance of the eye point from the center
621     before the center is pushed forward.*/
setMinimumDistance(const double & minimumDistance,bool relativeToModelSize)622 void OrbitManipulator::setMinimumDistance( const double& minimumDistance, bool relativeToModelSize )
623 {
624     _minimumDistance = minimumDistance;
625     setRelativeFlag( _minimumDistanceFlagIndex, relativeToModelSize );
626 }
627 
628 
629 /** Get the minimum distance of the eye point from the center
630     before the center is pushed forward.*/
getMinimumDistance(bool * relativeToModelSize) const631 double OrbitManipulator::getMinimumDistance( bool *relativeToModelSize ) const
632 {
633     if( relativeToModelSize )
634         *relativeToModelSize = getRelativeFlag( _minimumDistanceFlagIndex );
635 
636     return _minimumDistance;
637 }
638