1 /* Copyright (C) 1992-1998 The Geometry Center
2  * Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips
3  *
4  * This file is part of Geomview.
5  *
6  * Geomview is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published
8  * by the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * Geomview is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with Geomview; see the file COPYING.  If not, write
18  * to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
19  * USA, or visit http://www.gnu.org.
20  */
21 
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25 
26 #if 0
27 static char copyright[] = "Copyright (C) 1992-1998 The Geometry Center\n\
28 Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips";
29 #endif
30 
31 
32 /* Authors: Stuart Levy, Tamara Munzner, Mark Phillips */
33 
34 #include <stdio.h>
35 #include <math.h>
36 #include <string.h>
37 #include "mg.h"
38 #include "mouse.h"
39 #include "drawer.h"
40 #include "event.h"
41 #include "ui.h"
42 #include "motion.h"
43 #include "transform.h"
44 #include "space.h"
45 #include "comm.h"
46 #include "lang.h"
47 
48 typedef int (*ControlFunc)(int action, float x,float y,float t, float dx,float dy,float dt);
49 
50 
51 
52 typedef struct boundctl {
53   char *buttons;
54   ControlFunc function;
55   float rate;
56 } boundctl;
57 
58 /* These macros are passed to DTC and DSC as "functions".  As a special
59    case you can pass expressions like ".5 * X" which works because it
60    expands properly.  Be careful! */
61 
62 #undef X
63 #undef Y
64 #undef Z
65 
66 #define	ANGULAR 0
67 #define	LINEAR  1
68 
69 #define X mot.x
70 #define Y mot.y
71 #define	Z mot.z
72 #define ZERO  0
73 
74 #define	FAST 1.0
75 #define SLOW 0.1
76 
77 
78 static int minterp_switch(Event *event, boundctl *ctls);
79 
80 /* Define Transform Control */
81 #define DTC(name, interp, type, moving, center, frame, fx, fy, fz)	\
82 int name(int action, float x, float y, float t, float dx, float dy, float dt) \
83 {									\
84   Point3 mot;								\
85   int useframe = interp(action, center, frame, dx, dy, &mot);		\
86 									\
87   D1PRINT(("%s: action=%3d x=%8f y=%8f t=%8f dx=%8f dy=%8f dt=%8f\n",	\
88 	   #name, action, x, y, t, dx, dy, dt));			\
89 									\
90   (uistate.inertia ? gv_transform_incr : gv_transform)			\
91     ( moving, center, useframe, type, fx, fy, fz, dt, NO_KEYWORD );	\
92   return 1;								\
93 }
94 
95 /* Define Scale Control */
96 #define DSC(name, func, id, f)						      \
97 int name(int action, float x, float y, float t, float dx, float dy, float dt) \
98 {									      \
99   if (action > 0) /* button went down */				      \
100     dx = dy = 0;							      \
101   func( id,  f(dx,dy) );                                                      \
102   return 1;								      \
103 }
104 
maybe_scale(int id,float s)105 static void maybe_scale(int id, float s)
106 {
107   /* If ND-viewing is active, then a scaling motion is very confusing
108      because it only applies to one 3d sub-space. Instead, I change
109      the behaviour of the "scale" button to directly change the
110      ND-transform of "id". FIXME. We should handle this case as motion,
111      but that would require quite some hacks. cH.
112    */
113   if (drawerstate.NDim > 0) {
114     gv_scale(id, s, s, s);
115   } else {
116     /* only allow mouse scaling in Euclidean space */
117     if (spaceof(id) == TM_EUCLIDEAN) {
118       gv_transform(id, CENTERID, FOCUSID,
119 		   SCALE_KEYWORD, s, s, s, 0, NO_KEYWORD);
120     }
121     return;
122   }
123 }
124 
scaleexp(float dx,float dy)125 static float scaleexp(float dx, float dy)
126 {
127   return exp((dx+dy)/2);
128 }
129 
130 /*
131  * Construct "mot" motion-scaling vector:
132  *   mot->z scales motions into the screen: is the frame camera's focal length.
133  *   mot->x and mot->y scale motions in the screen plane:
134  *	if 'linear', they're spatial distances (for translations);
135  *	otherwise, they're tangents of half-angles (for rotations).
136  */
getcaminfo(int frame,int linear,Point3 * mot)137 static float getcaminfo(int frame, int linear, Point3 *mot)
138 {
139   float aspect = 1;
140   float focallen = 3;
141   int perspective = 1;
142   float halfyfield = .5;
143   DView *dv = (DView *)drawer_get_object(frame);
144   if(dv != NULL && ISCAM(dv->id)) {
145     CamGet(dv->cam, CAM_ASPECT, &aspect);
146     CamGet(dv->cam, CAM_FOCUS, &focallen);
147     CamGet(dv->cam, CAM_PERSPECTIVE, &perspective);
148     CamGet(dv->cam, CAM_HALFYFIELD, &halfyfield);
149   }
150   if(perspective && linear) halfyfield *= focallen;
151   else if(!perspective && !linear) halfyfield /= focallen;
152   mot->z = focallen;
153   mot->y = halfyfield;
154   mot->x = halfyfield * aspect;
155   return aspect;
156 }
157 
158 /*
159  * Apply constraints to motion.
160  * Change sign if we're moving in the object's own system.
161  * Return the appropriate frame of motion (the camera or SELF).
162  */
constrain(Point3 * mot,int framecamera)163 static int constrain(Point3 *mot, int framecamera)
164 {
165   if(uistate.constrained) {
166     if(fabs(mot->x) < fabs(mot->y)) mot->x = 0;
167     else mot->y = 0;
168   }
169   if(uistate.ownmotion)
170     return SELF;
171   return framecamera;
172 }
173 
174 static int
ROTATION(int action,int center,int frame,float dx,float dy,Point3 * mot)175 ROTATION(int action, int center, int frame, float dx, float dy, Point3 *mot)
176 {
177   if (action > 0) /* button went down */
178     mot->x = mot->y = mot->z = 0;
179   else {
180     float aspect = getcaminfo(frame, ANGULAR, mot);
181     if(drawer_idmatch(center, frame)) {
182 	/* If we're rotating about the camera itself (e.g. in fly mode), try to
183 	 * track the mouse: scale rotation according to angular field of view.
184 	 */
185 	mot->x *= -dy / aspect;
186 	mot->y *= dx * aspect;
187     } else {
188 	/* Otherwise, just adjust for non-square windows & hope for the best. */
189 	mot->x = -dy;
190 	mot->y = dx * aspect;
191     }
192     mot->z = .5*M_PI*(dx+dy);	/* Scale Z-axis motion according to mouse x+y */
193 				/* One full window width = half rotation */
194   }
195   return constrain(mot, frame);
196 }
197 
198 static int
TRANSLATION(int action,int center,int frame,float dx,float dy,Point3 * mot)199 TRANSLATION(int action, int center, int frame, float dx, float dy, Point3 *mot)
200 {
201   if(action > 0)  /* button went down */
202     mot->x = mot->y = mot->z = 0;
203   else {
204     getcaminfo(frame, LINEAR, mot);
205     mot->x *= dx;
206     mot->y *= dy;
207     if(spaceof(frame) != TM_EUCLIDEAN)
208 	mot->z = 1;	/* This is arbitrary; avoid moving too fast. */
209     mot->z *= .125 * (dx + dy);
210   }
211   return constrain(mot, frame);
212 }
213 
214 static int
STRANSLATION(int action,int center,int frame,float dx,float dy,Point3 * mot)215 STRANSLATION(int action, int center, int frame, float dx, float dy, Point3 *mot)
216 {
217   if(action > 0)  /* button went down */
218     mot->x = mot->y = mot->z = 0;
219   else {
220     getcaminfo(frame, LINEAR, mot);
221     mot->x *= dx;
222     mot->y *= dy;
223     mot->z = .125 * sinh(dx + dy);
224   }
225   return constrain(mot, frame);
226 }
227 
228 #if 0
229 static int
230 SCALING(int action, int center, int frame, float dx, float dy, Point3 *mot)
231 {
232   float scale;
233   if(action > 0)  /* button went down */
234     mot->x = mot->y = mot->z = 1;
235   else {
236     scale = .5*(dx + dy);
237     mot->x = mot->y = mot->z = 1+scale;
238   }
239   return uistate.ownmotion ? SELF : frame;
240 }
241 #endif
242 
243 /*
244 Kludge to make mouse-driven hyperbolic translations work correctly.
245 If the selected center is literally "target", then translations
246 are done with respect to the universe origin, rather than the object's
247 origin.  This is harmless in euclidean mode and  important in hyperbolic mode
248 so that wild spinning behavior does not occur. This change was originally
249 in 1.70.1.1, and got lost in the shuffle. (Stuart and Tamara 5/14/93)
250 
251 Thus, Use TCENTERID as center for all translations.
252 */
253 
254 #define TCENTERID (uistate.centerid == TARGETID && \
255 		    drawerstate.space == TM_HYPERBOLIC ? UNIVERSE : CENTERID)
256 
257 /*
258  * CONTROLS:
259  * A control is a function which interprets a single mouse button-combination
260  * action.  Controls don't know which button was pressed.
261  * Eventually, picking should be a control.
262 */
263 
264 /*
265  * PLEASE, DO NOT CHANGE the controls without reading these naming conventions.
266  *
267  * TEST any changes made in hyperbolic and spherical space before installation
268  *
269  * the naming convention for controls:
270  * controls start with 'ctl_'
271  * next comes a letter describing the kind of motion,
272  *  e.g. 'r' for rotation or 't' for translation
273  * next comes letters describing which axes are involved.
274  * next comes three letters describing which objects are involved.
275  * 'f' stands for FOCUSID, 'c' stands for CENTERID, 't' stands for TARGETID,
276  * 'g' stands for GEOMID(uistate.targetgeom), and
277  * 'C' stands for CAMID(uistate.targetcam). (there are none of these now)
278  * finally come letters describing how the input is transformed into the
279  * movement:
280  * 'l' stands for linear
281  */
282 DTC(ctl_rxy_tcf_l, ROTATION, ROTATE_KEYWORD, TARGETID, CENTERID, FOCUSID,
283     X, Y, ZERO)
284 
285 DTC(ctl_rz_tcf_l, ROTATION, ROTATE_KEYWORD, TARGETID, CENTERID, FOCUSID,
286     ZERO, ZERO, Z)
287 
288 DTC(ctl_txy_tcf_l, TRANSLATION, TRANSLATE_KEYWORD, TARGETID, TCENTERID, FOCUSID,
289     X, Y, ZERO)
290 
291 DTC(ctl_tz_tcf_l, TRANSLATION, TRANSLATE_KEYWORD, TARGETID, TCENTERID, FOCUSID,
292     ZERO, ZERO, Z)
293 
294 DTC(ctl_tsxy_tcf_l, STRANSLATION, TRANSLATE_SCALED_KEYWORD, TARGETID, TCENTERID, FOCUSID,
295     X, Y, ZERO)
296 
297 DTC(ctl_tsz_tcf_l, STRANSLATION, TRANSLATE_SCALED_KEYWORD, TARGETID, TCENTERID, FOCUSID,
298     ZERO, ZERO, Z)
299 
300 DTC(ctl_rxy_fff_l, ROTATION, ROTATE_KEYWORD, FOCUSID, FOCUSID, FOCUSID,
301     X, Y, ZERO)
302 
303 DTC(ctl_tz_fff_l, TRANSLATION, TRANSLATE_KEYWORD, FOCUSID, FOCUSID, FOCUSID,
304     ZERO, ZERO, -Z)
305 
306 DTC(ctl_tsz_fcf_l, STRANSLATION, TRANSLATE_SCALED_KEYWORD, FOCUSID, TCENTERID, FOCUSID,
307     ZERO, ZERO, -Z)
308 
309 DTC(ctl_tswz_fff_l, TRANSLATION, TRANSLATE_KEYWORD,FOCUSID,FOCUSID,FOCUSID,
310     ZERO, ZERO, -Z)
311 
312 DTC(ctl_rxy_fcf_l, ROTATION, ROTATE_KEYWORD, FOCUSID, CENTERID, FOCUSID,
313     -X, -Y, ZERO)
314 
315 DSC(ctl_z_l, gv_zoom, FOCUSID, scaleexp)
316 
317    /* maybe_scale(), defined above, prevents scaling in non-Euclidean spaces */
318 DSC(ctl_s_l, maybe_scale, TARGETGEOMID, scaleexp)
319 
320 
321 /*
322  * Bindings:
323  * Controls are bound together in useful functional groups (motion modes).
324  * This binding is really a part of the user interface.
325  */
326 
327 boundctl bound_rotations[] = {
328   {"leftmouse", ctl_rxy_tcf_l, FAST},
329   {"middlemouse", ctl_rz_tcf_l, FAST},
330   {"shift leftmouse", ctl_rxy_tcf_l, SLOW},
331   {"shift middlemouse", ctl_rz_tcf_l, SLOW},
332   {NULL, NULL}
333 };
334 
335 boundctl bound_translations[] = {
336   {"leftmouse", ctl_txy_tcf_l, FAST},
337   {"middlemouse", ctl_tz_tcf_l, FAST},
338   {"shift leftmouse", ctl_txy_tcf_l, SLOW},
339   {"shift middlemouse", ctl_tz_tcf_l, SLOW},
340   {NULL, NULL}
341 };
342 
343 boundctl bound_stranslations[] = {	/* Not used now - slevy 10/93 */
344   {"leftmouse", ctl_tsxy_tcf_l, FAST},
345   {"middlemouse", ctl_tsz_tcf_l, FAST},
346   {"shift leftmouse", ctl_tsxy_tcf_l, SLOW},
347   {"shift middlemouse", ctl_tsz_tcf_l, SLOW},
348   {NULL, NULL}
349 };
350 
351 boundctl bound_fly[] = {
352   {"leftmouse", ctl_rxy_fff_l, FAST},
353   {"middlemouse", ctl_tswz_fff_l, FAST},
354   {"shift leftmouse", ctl_rxy_fff_l, SLOW},
355   {"shift middlemouse", ctl_tswz_fff_l, SLOW},
356   {NULL, NULL}
357 };
358 
359 boundctl bound_orbit[] = {
360   {"leftmouse", ctl_rxy_fcf_l, FAST},
361   {"middlemouse", ctl_tsz_fcf_l, FAST},
362   {"shift leftmouse", ctl_rxy_fcf_l, SLOW},
363   {"shift middlemouse", ctl_tsz_fcf_l, SLOW},
364   {NULL, NULL}
365 };
366 
367 boundctl bound_zoom[] = {
368   {"leftmouse", ctl_z_l, FAST},
369   {NULL, NULL}
370 };
371 
372 boundctl bound_scale[] = {
373   {"leftmouse", ctl_s_l, FAST},
374   {NULL, NULL}
375 };
376 
minterp_rotate(Event * event)377 int minterp_rotate(Event *event)
378 { return minterp_switch(event, bound_rotations);}
379 
minterp_translate(Event * event)380 int minterp_translate(Event *event) /* not currently used */
381 {return minterp_switch(event, bound_translations);}
382 
minterp_stranslate(Event * event)383 int minterp_stranslate(Event *event)
384 {return minterp_switch(event, bound_stranslations);}
385 
minterp_fly(Event * event)386 int minterp_fly(Event *event)
387 {return minterp_switch(event, bound_fly);}
388 
minterp_orbit(Event * event)389 int minterp_orbit(Event *event)
390 {return minterp_switch(event, bound_orbit);}
391 
minterp_zoom(Event * event)392 int minterp_zoom(Event *event)
393 {return minterp_switch(event, bound_zoom);}
394 
minterp_scale(Event * event)395 int minterp_scale(Event *event)
396 {return minterp_switch(event, bound_scale);}
397 
398 #if EXPERIMENTAL_MOTION_AVERAGING
399 /* Ring buffer for motion averaging.
400  * For estimating speed for inertial motion when we release the mouse.
401  * Just extrapolating the last mouse delta may fail if we're redrawing
402  * very quickly.  So we keep more samples, and extrapolate motion over the
403  * last redraw cycle or last human-reasonable time (.1 second),
404  * whichever is longer.
405  */
406 #define NMOTE  8		/* Must be a power of two */
407 #define MODMOTE(x) ((x) & (NMOTE-1))
408 static struct mote {
409         float dx, dy;
410 	long dt;
411 } mote[NMOTE];
412 static int mo;
413 #endif
414 
415 
minterp_switch(Event * event,boundctl * ctls)416 static int minterp_switch(Event *event, boundctl *ctls)
417 {
418   int i;
419   float x, y, dx, dy;
420   long dt;
421   char buttonsdown[100];
422 
423 PRINT_EVENT(("minterp_switch", event));
424 D1PRINT(("interp_switch: ctls->buttons = %s\n", ctls->buttons));
425 
426   strcpy(buttonsdown, "");
427   mousedisp(event, &dx, &dy, (unsigned long *)&dt, &drawerstate.winpos);
428 D1PRINT(("minterp_switch: mousedisp returned : dx = %10f, dy = %10f, dt = %ld\n", dx, dy, dt));
429   if(dt <= 0 && !(dt == 0 && dx == 0 && dy == 0)) {
430      /* Discredit it if --
431       *  time is flowing backwards, or
432       *  we're being asked for finite motion in zero time.
433       */
434      return 0;
435   }
436 #if EXPERIMENTAL_MOTION_AVERAGING
437   mo = MODMOTE(mo+1);
438   mote[mo].dx = dx;  mote[mo].dy = dy;  mote[mo].dt = dt;
439   if(event->val == 0) {
440 	/* Mouse release: use recently-averaged dt to extrapolate
441 	 * continuing motion.
442 	 */
443 	for(i = 1; i < NMOTE; i++) {
444 	    struct mote *m = &mote[MODMOTE(mo-i)];
445 
446 	    if(dt + m->dt > uistate.cursor_still)
447 		break;
448 	    dx += m->dx;
449 	    dy += m->dy;
450 	    dt += m->dt;
451 	}
452 	i;	/* For debugging */
453   }
454 #endif
455   mousemap(event->x, event->y, &x, &y, &drawerstate.winpos);
456   if (button.shift) strcat(buttonsdown, "shift ");
457   if (button.ctrl) strcat(buttonsdown, "ctrl ");
458   if (button.left) strcat(buttonsdown, "leftmouse ");
459   if (button.middle) strcat(buttonsdown, "middlemouse ");
460   if(buttonsdown[0] != '\0') buttonsdown[strlen(buttonsdown)-1] = '\0';
461 
462   for (i = 0; ctls[i].buttons != NULL; i++) {
463     if (!strcmp(ctls[i].buttons, buttonsdown)) {
464       return (ctls[i].function)(event->val, x,y,event->t, dx,dy,
465 		dt*.001/ctls[i].rate);
466     }
467   }
468   return 0;
469 }
470 
471 /*
472  * Local Variables: ***
473  * mode: c ***
474  * c-basic-offset: 2 ***
475  * End: ***
476  */
477