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

Revision 10209, 12.2 kB (checked in by robert, 6 years ago)

From Ulrich Hertlein, "please find attached a patch for TrackballManipulator? to fix zooming using the MBP touchpad. The old code would always zoom-in even when using the gesture to zoom-out.

Also attached are some code and documentation cleanups for GUIEventAdapter that collect related values (e.g. scrolling, tablet pen) in a struct.
"

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