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