From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / gltrackball.c
1 /* gltrackball, Copyright (c) 2002-2014 Jamie Zawinski <jwz@jwz.org>
2  * GL-flavored wrapper for trackball.c
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #include <math.h>
14 #include <stdlib.h>
15 #include <string.h>
16
17 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20
21 #ifdef HAVE_COCOA
22 # include "jwxyz.h"
23 #else  /* !HAVE_COCOA -- real X11 */
24 # include <X11/X.h>
25 # include <X11/Xlib.h>
26 # include <GL/gl.h>
27 #endif /* !HAVE_COCOA */
28
29 #ifdef HAVE_JWZGLES
30 # include "jwzgles.h"
31 #endif /* HAVE_JWZGLES */
32
33 # define Button4 4  /* WTF */
34 # define Button5 5
35 # define Button6 6
36 # define Button7 7
37
38 #include "trackball.h"
39 #include "gltrackball.h"
40
41 /* Bah, copied from ../fps.h */
42 #ifdef USE_IPHONE
43   extern double current_device_rotation (void);
44 #else
45 # define current_device_rotation() (0)
46 #endif
47
48
49 struct trackball_state {
50   int ow, oh;
51   double x, y;
52   double dx, dy, ddx, ddy;
53   GLfloat q[4];
54   int button_down_p;
55   int ignore_device_rotation_p;
56 };
57
58 /* Returns a trackball_state object, which encapsulates the stuff necessary
59    to make dragging the mouse on the window of a GL program do the right thing.
60  */
61 trackball_state *
62 gltrackball_init (int ignore_device_rotation_p)
63 {
64   trackball_state *ts = (trackball_state *) calloc (1, sizeof (*ts));
65   if (!ts) return 0;
66   ts->ignore_device_rotation_p = ignore_device_rotation_p;
67   trackball (ts->q, 0, 0, 0, 0);
68   return ts;
69 }
70
71 /* Reset the trackball to the default unrotated state.
72  */
73 void
74 gltrackball_reset (trackball_state *ts)
75 {
76   memset (ts, 0, sizeof(*ts));
77   trackball (ts->q, 0, 0, 0, 0);
78 }
79
80
81 /* Device rotation interacts very strangely with mouse positions.
82    I'm not entirely sure this is the right fix.
83  */
84 static void
85 adjust_for_device_rotation (trackball_state *ts,
86                             double *x, double *y, double *w, double *h)
87 {
88   int rot = (int) current_device_rotation();
89   int swap;
90
91   if (ts->ignore_device_rotation_p) return;
92
93   while (rot <= -180) rot += 360;
94   while (rot >   180) rot -= 360;
95
96   if (rot > 135 || rot < -135)          /* 180 */
97     {
98       *x = *w - *x;
99       *y = *h - *y;
100     }
101   else if (rot > 45)                    /* 90 */
102     {
103       swap = *x; *x = *y; *y = swap;
104       swap = *w; *w = *h; *h = swap;
105       *x = *w - *x;
106     }
107   else if (rot < -45)                   /* 270 */
108     {
109       swap = *x; *x = *y; *y = swap;
110       swap = *w; *w = *h; *h = swap;
111       *y = *h - *y;
112     }
113 }
114
115
116 /* Begin tracking the mouse: Call this when the mouse button goes down.
117    x and y are the mouse position relative to the window.
118    w and h are the size of the window.
119  */
120 void
121 gltrackball_start (trackball_state *ts, int x, int y, int w, int h)
122 {
123   ts->x = x;
124   ts->y = y;
125   ts->button_down_p = 1;
126   ts->dx = ts->ddx = 0;
127   ts->dy = ts->ddy = 0;
128 }
129
130 /* Stop tracking the mouse: Call this when the mouse button goes up.
131  */
132 void
133 gltrackball_stop (trackball_state *ts)
134 {
135   ts->button_down_p = 0;
136 }
137
138 static void
139 gltrackball_track_1 (trackball_state *ts,
140                      double x, double y,
141                      int w, int h)
142 {
143   double X = x;
144   double Y = y;
145   double W = w, W2 = w;
146   double H = h, H2 = h;
147   float q2[4];
148   double ox = ts->x;
149   double oy = ts->y;
150
151   ts->x = x;
152   ts->y = y;
153
154   adjust_for_device_rotation (ts, &ox, &oy, &W,  &H);
155   adjust_for_device_rotation (ts, &X,  &Y,  &W2, &H2);
156
157   trackball (q2,
158              (2 * ox - W) / W,
159              (H - 2 * oy) / H,
160              (2 * X - W)  / W,
161              (H - 2 * Y)  / H);
162
163   add_quats (q2, ts->q, ts->q);
164 }
165
166
167 /* Track the mouse: Call this each time the mouse moves with the button down.
168    x and y are the new mouse position relative to the window.
169    w and h are the size of the window.
170  */
171 void
172 gltrackball_track (trackball_state *ts, int x, int y, int w, int h)
173 {
174   double dampen = 0.01;  /* This keeps it going for about 3 sec */
175   ts->dx = x - ts->x;
176   ts->dy = y - ts->y;
177   ts->ddx = ts->dx * dampen;
178   ts->ddy = ts->dy * dampen;
179   ts->ow = w;
180   ts->oh = h;
181   gltrackball_track_1 (ts, x, y, w, h);
182 }
183
184
185 static void
186 gltrackball_dampen (double *n, double *dn)
187 {
188   int pos = (*n > 0);
189   *n -= *dn;
190   if (pos != (*n > 0))
191     *n = *dn = 0;
192 }
193
194
195 /* Execute the rotations current encapsulated in the trackball_state:
196    this does something analagous to glRotatef().
197  */
198 void
199 gltrackball_rotate (trackball_state *ts)
200 {
201   GLfloat m[4][4];
202   if (!ts->button_down_p &&
203       (ts->ddx != 0 ||
204        ts->ddy != 0))
205     {
206       /* Apply inertia: keep moving in the same direction as the last move. */
207       gltrackball_track_1 (ts, 
208                            ts->x + ts->dx,
209                            ts->y + ts->dy,
210                            ts->ow, ts->oh);
211
212       /* Dampen inertia: gradually stop spinning. */
213       gltrackball_dampen (&ts->dx, &ts->ddx);
214       gltrackball_dampen (&ts->dy, &ts->ddy);
215     }
216
217   build_rotmatrix (m, ts->q);
218   glMultMatrixf (&m[0][0]);
219 }
220
221
222 /* Call this when a mouse-wheel click is detected.
223    Clicks act like horizontal or vertical drags.
224    Percent is the length of the drag as a percentage of the screen size.
225    Button is 'Button4' or 'Button5' (for the vertical wheel)
226    or 'Button5' or 'Button6' (for the horizontal wheel).
227    If `flip_p' is true, swap the horizontal and vertical axes.
228  */
229 void
230 gltrackball_mousewheel (trackball_state *ts,
231                         int button, int percent, int flip_p)
232 {
233   int up_p;
234   int horizontal_p;
235   int mx, my, move, scale;
236
237 #ifdef HAVE_COCOA
238   flip_p = 0;      /* MacOS has already handled this. */
239 #endif
240
241   switch (button) {
242   case Button4: up_p = 1; horizontal_p = 0; break;
243   case Button5: up_p = 0; horizontal_p = 0; break;
244   case Button6: up_p = 1; horizontal_p = 1; break;
245   case Button7: up_p = 0; horizontal_p = 1; break;
246   default: abort(); break;
247   }
248
249   if (flip_p)
250     {
251       horizontal_p = !horizontal_p;
252       up_p = !up_p;
253     }
254
255   scale = mx = my = 1000;
256   move = (up_p
257           ? floor (scale * (1.0 - (percent / 100.0)))
258           : ceil  (scale * (1.0 + (percent / 100.0))));
259   if (horizontal_p) mx = move;
260   else              my = move;
261   gltrackball_start (ts, scale, scale, scale*2, scale*2);
262   gltrackball_track (ts, mx, my, scale*2, scale*2);
263 }
264
265 void
266 gltrackball_get_quaternion (trackball_state *ts, float q[4])
267 {
268   int i;
269   for (i=0; i<4; i++)
270     q[i] = ts->q[i];
271 }
272
273
274 /* A utility function for event-handler functions:
275    Handles the various motion and click events related to trackballs.
276    Returns True if the event was handled.
277  */
278 Bool
279 gltrackball_event_handler (XEvent *event,
280                            trackball_state *ts,
281                            int window_width, int window_height,
282                            Bool *button_down_p)
283 {
284   if (event->xany.type == ButtonPress &&
285       event->xbutton.button == Button1)
286     {
287       *button_down_p = True;
288       gltrackball_start (ts,
289                          event->xbutton.x, event->xbutton.y,
290                          window_width, window_height);
291       return True;
292     }
293   else if (event->xany.type == ButtonRelease &&
294            event->xbutton.button == Button1)
295     {
296       *button_down_p = False;
297       gltrackball_stop (ts);
298       return True;
299     }
300   else if (event->xany.type == ButtonPress &&
301            (event->xbutton.button == Button4 ||
302             event->xbutton.button == Button5 ||
303             event->xbutton.button == Button6 ||
304             event->xbutton.button == Button7))
305     {
306       gltrackball_mousewheel (ts, event->xbutton.button, 10,
307                               !!event->xbutton.state);
308       return True;
309     }
310   else if (event->xany.type == MotionNotify &&
311            *button_down_p)
312     {
313       gltrackball_track (ts,
314                          event->xmotion.x, event->xmotion.y,
315                          window_width, window_height);
316       return True;
317     }
318
319   return False;
320 }