root/OpenSceneGraph/trunk/src/osgGA/OrbitManipulator.cpp @ 13041

Revision 13041, 18.5 kB (checked in by robert, 3 years ago)

Ran script to remove trailing spaces and tabs

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