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