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

Revision 9891, 11.5 kB (checked in by robert, 5 years ago)

From Martin Beckett, "Modified osgGA::TrackballManipulator? so that osgGA::GUIEventAdapter::SCROLL performs a zoom.
Arbitrarily zooms 10% in/out for each click."

Note from Robert Osfield, flipped the orienation of the zoom to make the right mouse key zoom and the scroll wheel work in the same direction.

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