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