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