root/OpenSceneGraph/trunk/src/osgGA/TrackballManipulator.cpp @ 9896

Revision 9896, 11.5 kB (checked in by robert, 6 years ago)

From Martin Beckett, added get/setScrollWheelZoomDelta() support for controlling the mouse scroll wheel zoom delta.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1#include <osgGA/TrackballManipulator>
2#include <osg/Quat>
3#include <osg/Notify>
4#include <osg/BoundsChecking>
5
6using namespace osg;
7using namespace osgGA;
8
9TrackballManipulator::TrackballManipulator()
10{
11    _modelScale = 0.01f;
12    _minimumZoomScale = 0.05f;
13    _thrown = false;
14
15    _distance = 1.0f;
16    _trackballSize = 0.8f;
17    _zoomDelta = 0.1f;
18}
19
20
21TrackballManipulator::~TrackballManipulator()
22{
23}
24
25
26void TrackballManipulator::setNode(osg::Node* node)
27{
28    _node = node;
29    if (_node.get())
30    {
31        const osg::BoundingSphere& boundingSphere=_node->getBound();
32        _modelScale = boundingSphere._radius;
33    }
34    if (getAutoComputeHomePosition()) computeHomePosition();
35}
36
37
38const osg::Node* TrackballManipulator::getNode() const
39{
40    return _node.get();
41}
42
43
44osg::Node* TrackballManipulator::getNode()
45{
46    return _node.get();
47}
48
49
50void TrackballManipulator::home(double /*currentTime*/)
51{
52    if (getAutoComputeHomePosition()) computeHomePosition();
53    computePosition(_homeEye, _homeCenter, _homeUp);
54    _thrown = false;
55}
56
57void TrackballManipulator::home(const GUIEventAdapter& ea ,GUIActionAdapter& us)
58{
59    home(ea.getTime());
60    us.requestRedraw();
61    us.requestContinuousUpdate(false);
62}
63
64
65void TrackballManipulator::init(const GUIEventAdapter& ,GUIActionAdapter& )
66{
67    flushMouseEventStack();
68}
69
70
71void TrackballManipulator::getUsage(osg::ApplicationUsage& usage) const
72{
73    usage.addKeyboardMouseBinding("Trackball: Space","Reset the viewing position to home");
74    usage.addKeyboardMouseBinding("Trackball: +","When in stereo, increase the fusion distance");
75    usage.addKeyboardMouseBinding("Trackball: -","When in stereo, reduce the fusion distance");
76}
77
78bool TrackballManipulator::handle(const GUIEventAdapter& ea,GUIActionAdapter& us)
79{
80    switch(ea.getEventType())
81    {
82        case(GUIEventAdapter::FRAME):
83            if (_thrown)
84            {
85                if (calcMovement()) us.requestRedraw();
86            }
87            return false;
88        default:
89            break;
90    }
91
92    if (ea.getHandled()) return false;
93
94    switch(ea.getEventType())
95    {
96        case(GUIEventAdapter::PUSH):
97        {
98            flushMouseEventStack();
99            addMouseEvent(ea);
100            if (calcMovement()) us.requestRedraw();
101            us.requestContinuousUpdate(false);
102            _thrown = false;
103            return true;
104        }
105
106        case(GUIEventAdapter::RELEASE):
107        {
108            if (ea.getButtonMask()==0)
109            {
110           
111                double timeSinceLastRecordEvent = _ga_t0.valid() ? (ea.getTime() - _ga_t0->getTime()) : DBL_MAX;
112                if (timeSinceLastRecordEvent>0.02) flushMouseEventStack();
113
114                if (isMouseMoving())
115                {
116                    if (calcMovement())
117                    {
118                        us.requestRedraw();
119                        us.requestContinuousUpdate(true);
120                        _thrown = true;
121                    }
122                }
123                else
124                {
125                    flushMouseEventStack();
126                    addMouseEvent(ea);
127                    if (calcMovement()) us.requestRedraw();
128                    us.requestContinuousUpdate(false);
129                    _thrown = false;
130                }
131
132            }
133            else
134            {
135                flushMouseEventStack();
136                addMouseEvent(ea);
137                if (calcMovement()) us.requestRedraw();
138                us.requestContinuousUpdate(false);
139                _thrown = false;
140            }
141            return true;
142        }
143
144        case(GUIEventAdapter::DRAG):
145        case(GUIEventAdapter::SCROLL):
146        {
147            addMouseEvent(ea);
148            if (calcMovement()) us.requestRedraw();
149            us.requestContinuousUpdate(false);
150            _thrown = false;
151            return true;
152        }
153
154        case(GUIEventAdapter::MOVE):
155        {
156            return false;
157        }
158
159        case(GUIEventAdapter::KEYDOWN):
160            if (ea.getKey()== GUIEventAdapter::KEY_Space)
161            {
162                flushMouseEventStack();
163                _thrown = false;
164                home(ea,us);
165                return true;
166            }
167            return false;
168        case(GUIEventAdapter::FRAME):
169            if (_thrown)
170            {
171                if (calcMovement()) us.requestRedraw();
172            }
173            return false;
174        default:
175            return false;
176    }
177}
178
179
180bool TrackballManipulator::isMouseMoving()
181{
182    if (_ga_t0.get()==NULL || _ga_t1.get()==NULL) return false;
183
184    static const float velocity = 0.1f;
185
186    float dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
187    float dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
188    float len = sqrtf(dx*dx+dy*dy);
189    float dt = _ga_t0->getTime()-_ga_t1->getTime();
190
191    return (len>dt*velocity);
192}
193
194
195void TrackballManipulator::flushMouseEventStack()
196{
197    _ga_t1 = NULL;
198    _ga_t0 = NULL;
199}
200
201
202void TrackballManipulator::addMouseEvent(const GUIEventAdapter& ea)
203{
204    _ga_t1 = _ga_t0;
205    _ga_t0 = &ea;
206}
207
208void TrackballManipulator::setByMatrix(const osg::Matrixd& matrix)
209{
210    _center = osg::Vec3(0.0f,0.0f,-_distance)*matrix;
211    _rotation = matrix.getRotate();
212}
213
214osg::Matrixd TrackballManipulator::getMatrix() const
215{
216    return osg::Matrixd::translate(0.0,0.0,_distance)*osg::Matrixd::rotate(_rotation)*osg::Matrixd::translate(_center);
217}
218
219osg::Matrixd TrackballManipulator::getInverseMatrix() const
220{
221    return osg::Matrixd::translate(-_center)*osg::Matrixd::rotate(_rotation.inverse())*osg::Matrixd::translate(0.0,0.0,-_distance);
222}
223
224void TrackballManipulator::computePosition(const osg::Vec3& eye,const osg::Vec3& center,const osg::Vec3& up)
225{
226
227    osg::Vec3 lv(center-eye);
228
229    osg::Vec3 f(lv);
230    f.normalize();
231    osg::Vec3 s(f^up);
232    s.normalize();
233    osg::Vec3 u(s^f);
234    u.normalize();
235   
236    osg::Matrix rotation_matrix(s[0],     u[0],     -f[0],     0.0f,
237                                s[1],     u[1],     -f[1],     0.0f,
238                                s[2],     u[2],     -f[2],     0.0f,
239                                0.0f,     0.0f,     0.0f,      1.0f);
240                   
241    _center = center;
242    _distance = lv.length();
243    _rotation = rotation_matrix.getRotate().inverse();
244}
245
246
247bool TrackballManipulator::calcMovement()
248{
249    // mouse scroll is only a single event
250    if (_ga_t0.get()==NULL) return false;
251
252    float dx=0.0f;
253    float dy=0.0f;
254    unsigned int buttonMask=osgGA::GUIEventAdapter::NONE;
255
256    if (_ga_t0->getEventType()==GUIEventAdapter::SCROLL)
257    {
258        dy = _ga_t0->getScrollingMotion() == osgGA::GUIEventAdapter::SCROLL_UP ? _zoomDelta : -_zoomDelta;
259        buttonMask=GUIEventAdapter::SCROLL;
260    }
261    else 
262    {
263
264        if (_ga_t1.get()==NULL) return false;
265        dx = _ga_t0->getXnormalized()-_ga_t1->getXnormalized();
266        dy = _ga_t0->getYnormalized()-_ga_t1->getYnormalized();
267        float distance = sqrtf(dx*dx + dy*dy);
268       
269        // return if movement is too fast, indicating an error in event values or change in screen.
270        if (distance>0.5)
271        {
272            return false;
273        }
274       
275        // return if there is no movement.
276        if (distance==0.0f)
277        {
278            return false;
279        }
280
281        buttonMask = _ga_t1->getButtonMask();
282    }
283   
284    if (buttonMask==GUIEventAdapter::LEFT_MOUSE_BUTTON)
285    {
286
287        // rotate camera.
288
289        osg::Vec3 axis;
290        float angle;
291
292        float px0 = _ga_t0->getXnormalized();
293        float py0 = _ga_t0->getYnormalized();
294       
295        float px1 = _ga_t1->getXnormalized();
296        float py1 = _ga_t1->getYnormalized();
297       
298
299        trackball(axis,angle,px1,py1,px0,py0);
300
301        osg::Quat new_rotate;
302        new_rotate.makeRotate(angle,axis);
303       
304        _rotation = _rotation*new_rotate;
305
306        return true;
307
308    }
309    else if (buttonMask==GUIEventAdapter::MIDDLE_MOUSE_BUTTON ||
310        buttonMask==(GUIEventAdapter::LEFT_MOUSE_BUTTON|GUIEventAdapter::RIGHT_MOUSE_BUTTON))
311    {
312
313        // pan model.
314
315        float scale = -0.3f*_distance;
316
317        osg::Matrix rotation_matrix;
318        rotation_matrix.makeRotate(_rotation);
319
320        osg::Vec3 dv(dx*scale,dy*scale,0.0f);
321
322        _center += dv*rotation_matrix;
323       
324        return true;
325
326    }
327    else if ((buttonMask==GUIEventAdapter::RIGHT_MOUSE_BUTTON) || (buttonMask==GUIEventAdapter::SCROLL))
328    {
329
330        // zoom model.
331
332        float fd = _distance;
333        float scale = 1.0f+dy;
334        if (fd*scale>_modelScale*_minimumZoomScale)
335        {
336
337            _distance *= scale;
338
339        }
340        else
341        {
342
343            // notify(DEBUG_INFO) << "Pushing forward"<<std::endl;
344            // push the camera forward.
345            float scale = -fd;
346
347            osg::Matrix rotation_matrix(_rotation);
348
349            osg::Vec3 dv = (osg::Vec3(0.0f,0.0f,-1.0f)*rotation_matrix)*(dy*scale);
350
351            _center += dv;
352
353        }
354
355        return true;
356
357    }
358
359    return false;
360}
361
362
363/*
364 * This size should really be based on the distance from the center of
365 * rotation to the point on the object underneath the mouse.  That
366 * point would then track the mouse as closely as possible.  This is a
367 * simple example, though, so that is left as an Exercise for the
368 * Programmer.
369 */
370void TrackballManipulator::setTrackballSize(float size)
371{
372    _trackballSize = size;
373     osg::clampBetweenRange(_trackballSize,0.1f,1.0f,"TrackballManipulator::setTrackballSize(float)");
374}
375
376/*
377 * Ok, simulate a track-ball.  Project the points onto the virtual
378 * trackball, then figure out the axis of rotation, which is the cross
379 * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
380 * Note:  This is a deformed trackball-- is a trackball in the center,
381 * but is deformed into a hyperbolic sheet of rotation away from the
382 * center.  This particular function was chosen after trying out
383 * several variations.
384 *
385 * It is assumed that the arguments to this routine are in the range
386 * (-1.0 ... 1.0)
387 */
388void TrackballManipulator::trackball(osg::Vec3& axis,float& angle, float p1x, float p1y, float p2x, float p2y)
389{
390    /*
391     * First, figure out z-coordinates for projection of P1 and P2 to
392     * deformed sphere
393     */
394
395    osg::Matrix rotation_matrix(_rotation);
396
397
398    osg::Vec3 uv = osg::Vec3(0.0f,1.0f,0.0f)*rotation_matrix;
399    osg::Vec3 sv = osg::Vec3(1.0f,0.0f,0.0f)*rotation_matrix;
400    osg::Vec3 lv = osg::Vec3(0.0f,0.0f,-1.0f)*rotation_matrix;
401
402    osg::Vec3 p1 = sv * p1x + uv * p1y - lv * tb_project_to_sphere(_trackballSize, p1x, p1y);
403    osg::Vec3 p2 = sv * p2x + uv * p2y - lv * tb_project_to_sphere(_trackballSize, p2x, p2y);
404
405    /*
406     *  Now, we want the cross product of P1 and P2
407     */
408
409// Robert,
410//
411// This was the quick 'n' dirty  fix to get the trackball doing the right
412// thing after fixing the Quat rotations to be right-handed.  You may want
413// to do something more elegant.
414//   axis = p1^p2;
415axis = p2^p1;
416    axis.normalize();
417
418    /*
419     *  Figure out how much to rotate around that axis.
420     */
421    float t = (p2 - p1).length() / (2.0 * _trackballSize);
422
423    /*
424     * Avoid problems with out-of-control values...
425     */
426    if (t > 1.0) t = 1.0;
427    if (t < -1.0) t = -1.0;
428    angle = inRadians(asin(t));
429
430}
431
432
433/*
434 * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
435 * if we are away from the center of the sphere.
436 */
437float TrackballManipulator::tb_project_to_sphere(float r, float x, float y)
438{
439    float d, t, z;
440
441    d = sqrt(x*x + y*y);
442                                 /* Inside sphere */
443    if (d < r * 0.70710678118654752440)
444    {
445        z = sqrt(r*r - d*d);
446    }                            /* On hyperbola */
447    else
448    {
449        t = r / 1.41421356237309504880;
450        z = t*t / d;
451    }
452    return z;
453}
Note: See TracBrowser for help on using the browser.