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