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 <ctype.h>
35 #include <sys/types.h>
36 #include <sys/time.h>
37 #include <string.h>
38 #include <stdlib.h>
39 
40 #include "mg.h"
41 
42 #include "drawer.h"
43 #include "event.h"
44 #include "lang.h"
45 #include "ui.h"
46 #include "comm.h"
47 #include "pickP.h"
48 #include "transform.h"
49 #include "streampool.h"
50 #include "lights.h"
51 #include "mouse.h"
52 #include "rman.h"
53 
54 #define DOUBLECLICKTIME 333	/* millisecs between double clicks */
55 
56 EventState estate;	/* External motion state */
57 
58 struct button button;	/* Shift, etc. button state */
59 
60 void emit_pick(int pickedid, Pick *pick);
61 static int view_pick( DView *dv, int x, int y, Pick *pick );
62 
63 #define	ESC	'\033'
64 
65 struct num {
66   int has;	/* 0 if no number, or -1 (negative) or +1 (positive) */
67   int val;
68   int expon;
69 } number, onum;
70 
71 static int keyshorts = 1;
72 static int pickon = 1;
73 static int prefixid = NOID;
74 static float getreal(float defaultvalue);
75 static int getint(int defaultvalue);
76 /*static int toggle(int val);*/
77 static void tog_ap_flag(int id, int flagbit);
78 static int retarget(int defaultid);
79 
80 #define	SEQ(prefix, ch)  ((prefix)<<8 | (ch))
81 
82 static enum { KEYGEOM=0, KEYCAM=1, KEYNONE=2 } keymode = KEYNONE;
83 static int prefix = 0;
84 
85 static char Help[] = "\
86 Keyboard commands apply while cursor is in any graphics window and most \n\
87 control panels. Most commands allow one of the following selection prefixes \n\
88 (if none is provided the command applies to the current object): \n\
89    g  world geom	g#  #'th geom	g*  All geoms\n\
90    c  current camera	c#  #'th camera	c*  All cameras\n\
91 Many allow a numeric prefix:  if none they toggle or reset current value.\n\
92 Appearance:\n\
93  Draw:		     Shading:		Other:\n\
94   af  Faces		0as Constant	 av  eVert normals: always face viewer\n\
95   ae  Edges		1as Flat	#aw  Line Width (pixels)\n\
96   an  Normals		2as Smooth	#ac  edges Closer than faces(try 5-100)\n\
97   ab  Bounding Boxes	3as Smooth, non-lighted     al  Shade lines\n\
98   aV  Vectors		aT  allow transparency      at  Texture-mapping\n\
99  Color:			aC  allow concave polygons  aq  Texture quality\n\
100   Cf Ce Cn Cb CB   face/edge/normal/bbox/backgnd\n\
101 Motions:				      Viewing:\n\
102   r rotate	   [ Leftmouse=X-Y plane,	0vp Orthographic view\n"
103 #ifdef NeXT
104   "  t translate	     Alt-Left=Z axis,		1vp Perspective view\n"
105 #else
106   "  t translate	     Middle=Z axis,		1vp Perspective view\n"
107 #endif
108   "  z zoom FOV	     Shift=slow motion,		 vd Draw other views' cameras\n\
109   f fly		     in r/t modes.      ]	#vv field of View\n\
110   o orbit           [Left=steer, Middle=speed ]	#vn near clip distance\n\
111   s scale					#vf far clip distance\n\
112   w/W recenter/all				 v+ add new camera\n\
113   h/H halt/halt all				 vx cursor on/off\n\
114   @  select center of motion (e.g. g3@)		 vb backfacing poly cull on/off\n\
115 						#vl focal length\n\
116   L  Look At object				 v~ Software shading on/off\n\
117 show Panel:	Pm Pa Pl Po	main/appearance/lighting/obscure\n\
118 		Pt Pc PC Pf	tools/cameras/Commands/file-browser\n\
119 		Ps P-		saving/read commands from tty\n\
120 Lights:  ls le		Show lights / Edit Lights\n\
121 Metric:  me mh ms  	Euclidean Hyperbolic Spherical\n\
122 Model:   mv mp mc	Virtual Projective Conformal\n\
123 Other:\n\
124   N normalization < Pf  load geom/command file\n\
125    0N none	  > Ps  save something to file	ui  motion has inertia\n\
126    1N each	  TV	NTSC mode toggle	uc  constrained (X/Y) motion\n\
127    2N all	  				uo  motion in Own coord system\n\
128   Rightmouse-doubleclick  pick as current target object\n\
129   Shift-Rightmouse        pick interest (center) point\n"
130 #ifdef NeXT
131   "  Alt-Leftmouse is synonym for Rightmouse.\n"
132 #endif
133   ;
134 
135 void
print_help()136 print_help()
137 {
138   printf("\n%s", Help);
139   rman_do('?', 0,0);
140   fflush(stdout);
141 }
142 
143 void
event_init()144 event_init()
145 {
146   estate.motionproc = NULL;
147 }
148 
149 LDEFINE(event_mode, LVOID,
150 	"(event-mode     MODESTRING)\n\
151 	Set the mouse event (motion) mode; MODESTRING should be one of\n\
152 	the strings that appears in the motion mode browser (including\n\
153 	the keyboard shortcut, e.g. \"[r] Rotate\").")
154 {
155   static Event ev_enter = { EMODEENTER, 0, 0, 0, 0 };
156   static Event ev_exit = { EMODEEXIT, 0, 0, 0, 0 };
157   char *modename;
158 
159   LDECLARE(("event-mode", LBEGIN,
160 	    LSTRING, &modename,
161 	    LEND));
162 
163   if ( estate.motionproc != NULL ) {
164     estate.motionproc(&ev_exit);
165   }
166   estate.motionproc =
167     uistate.modes[uistate.mode_current = ui_mode_index(modename) ];
168   D1PRINT(("gv_event_mode: estate.motionproc <- %1x\n", estate.motionproc));
169   ui_event_mode( modename );
170   if ( estate.motionproc != NULL ) {
171     estate.motionproc(&ev_enter);
172   }
173   return Lt;
174 }
175 
176 
177 
178 /*
179  * Report time elapsed since the epoch (or since the program began if
180  * since == NULL).   Possibly remember the current time in "nextepoch".
181  * Time is measured in floating-point seconds.
182  */
183 float
elapsed(float * since,float * nextepoch)184 elapsed(float *since, float *nextepoch)
185 {
186   static struct timeval t0 = { 0, 0 };
187   struct timeval tnow;
188   float now = 0;
189   float sincetime = 0;
190 
191   gettimeofday(&tnow, NULL);
192   if(t0.tv_sec == 0) {
193     t0 = tnow;
194     tnow.tv_usec++;
195   }
196   now = tnow.tv_sec - t0.tv_sec + 1e-6*(tnow.tv_usec - t0.tv_usec);
197   if(since) {
198     if((sincetime = *since) == 0)
199       sincetime = *since = now;
200   }
201   if(nextepoch) *nextepoch = now;
202   return now - sincetime;
203 }
204 
205 LDEFINE(pick_invisible, LVOID,
206 	"(pick-invisible [yes|no])\n\
207 	Selects whether picks should be sensitive to objects whose appearance\n\
208 	makes them invisible; default yes.\n\
209 	With no arguments, returns current status.")
210 {
211   Keyword kw = NOT_A_KEYWORD;
212 
213   LDECLARE(("pick-invisible", LBEGIN,
214 	    LOPTIONAL,
215 	    LKEYWORD, &kw,
216 	    LEND));
217 
218   if(kw == NOT_A_KEYWORD) {
219     return uistate.pick_invisible ? Lt : Lnil;
220   }
221 
222   uistate.pick_invisible = boolval("pick_invisible", kw);
223 
224   return Lt;
225 }
226 
227 LDEFINE(rawevent, LVOID,
228 	"(rawevent       dev val x y t)\n\
229 	Enter the specified raw event into the event queue.  The\n\
230 	arguments directly specify the members of the event structure\n\
231 	used internally by geomview.  This is the lowest level event\n\
232 	handler and is not intended for general use.")
233 /*
234   This used to be dispatch_event().
235 */
236 {
237   int id;
238   int apfl;
239   DrawerKeyword k;
240   int err = 0;
241   float v;
242   Appearance *ap;
243   DView *dv;
244   DGeom *dg;
245   char *s;
246 
247   Event event;
248   LDECLARE(("rawevent", LBEGIN,
249 	    LINT, &event.dev,
250 	    LINT, &event.val,
251 	    LINT, &event.x,
252 	    LINT, &event.y,
253 	    LLONG, &event.t,
254 	    LEND));
255 
256   PRINT_EVENT(("in gv_rawevent", &event));
257 
258   /*
259    * Call the current motion proc, if any.  This proc returns 1 if it
260    * used the event, in which case we don't do any further processing.
261    */
262   D1PRINT(("gv_rawevent: estate.motionproc = %x\n", estate.motionproc));
263   if ( estate.motionproc != NULL ) {
264     D1PRINT(("gv_rawevent:   calling estate.motionproc\n"));
265     if ( estate.motionproc(&event) ) {
266       D1PRINT(("gv_rawevent:     returning Lt\n"));
267       return Lt;
268     }
269     D1PRINT(("gv_rawevent:     falling through\n"));
270   }
271 
272   /* The rightmouse and doubleclick is now hardcoded but should be
273      bindable through lang later, through a control mechanism similar to
274      current motionproc stuff */
275 
276   if (event.dev == ERIGHTMOUSE) {
277     if ((event.val > 0) && pickon) {
278       static unsigned long int lastt = 0;
279       Pick *pick = PickSet(NULL, PA_WANT,
280 			   uistate.pick_invisible ? PW_EDGE|PW_VERT|PW_FACE
281 			   : PW_VISIBLE|PW_EDGE|PW_VERT|PW_FACE,
282 			   PA_END);
283       int pickedid = view_pick( (DView *)drawer_get_object(FOCUSID),
284 				event.x, event.y, pick );
285 
286       if(button.shift) {
287 	/* Could change FOCUSID here to ALLCAMS,
288 	 * to force setting everyone's focal length to
289 	 * their distance from the pick.
290 	 */
291 	if(pickedid != NOID)
292 	  make_center_from_pick("CENTER", pick, FOCUSID);
293 	else
294 	  gv_ui_center(TARGETID);
295       } else {
296 	if(pickedid != NOID)
297 	  emit_pick(pickedid, pick);
298 	if (event.t - lastt < DOUBLECLICKTIME) {
299 	  lastt = 0;
300 	  gv_ui_target( pickedid!=NOID ? pickedid : WORLDGEOM, IMMEDIATE );
301 	}
302       }
303       PickDelete(pick);
304       lastt = event.t;
305     }
306   }
307 
308   if(!isascii(event.dev))
309     return Lt;
310 
311 
312   if (!keyshorts)  /* are keyboard shortcuts on? :-) */
313     return Lt;     /* no? then we don't want to process them... */
314 
315   /* Only keyboard events from here on down */
316 
317   ui_keyboard(event.dev);
318   if(event.dev >= '0' && event.dev <= '9') {
319     if(!number.has) {
320       number.val = 0;
321       number.has = 1;
322     }
323     number.val = 10*number.val + (event.dev - '0');
324     if(number.expon) number.expon++;
325     prefix = 0;
326   } else {
327 
328     id = GEOMID(uistate.targetgeom);
329 
330   rescan:
331 
332     switch(SEQ(prefix, event.dev)) {
333 
334     case '-':
335     case '*':
336       number.has = -1;
337       number.expon = 0;
338       number.val = 0;
339       prefix = 0;
340       goto keepmode;
341 
342     case '.':
343       number.expon = 1;
344       prefix = 0;
345       goto keepmode;
346 
347 
348     case 'g': keymode = KEYGEOM; goto gotmode; /* Select geom:  'g' prefix */
349     case 'c': keymode = KEYCAM; goto gotmode;  /* Select camera: 'c' prefix */
350     gotmode:
351       onum = number;
352       number.has = 0;
353       prefix = 0;
354       goto keepmode;
355 
356     case 'p':
357       {
358 	int id;
359 
360 	if (pickon) {
361 	  if (keymode == KEYNONE) {
362 	    id = gv_rawpick(FOCUSID, event.x, event.y);
363 	    if (id == NOID) id = WORLDGEOM;
364 	  } else {
365 	    id = retarget(NOID);
366 	  }
367 
368 	  gv_ui_target(id, IMMEDIATE);
369 	}
370       }
371 
372     case '@':
373       gv_ui_center(retarget(uistate.centerid));
374       break;
375 
376     case 'N':
377       id = retarget(GEOMID(uistate.targetgeom));
378       if(!number.has) {
379 	dg = (DGeom *)drawer_get_object(id);
380 	if(dg) number.val = dg->normalization == NONE ? EACH : NONE;
381       }
382       drawer_int(id, DRAWER_NORMALIZATION, number.val);
383       break;
384 
385     case '<':
386       s = "Load"; number.val = 1; goto pickpanel;	/* Load file */
387     case '>':
388       s = "Save"; number.val = 1; goto pickpanel;	/* Save State */
389 
390       /* use bounding box center as CENTER position */
391     case 'B':
392       gv_ui_center_origin(uistate.bbox_center
393 			  ? ORIGIN_KEYWORD : BBOX_CENTER_KEYWORD);
394       break;
395 
396       /* Halt current object */
397     case 'h':
398       drawer_stop(retarget(uistate.targetid)); break;
399 
400     case 'H':	/* Halt Everything */
401       drawer_stop(NOID); break;
402 
403     case 'w':     /* Recenter current thing */
404       drawer_center(retarget(uistate.targetid)); break;
405 
406     case 'W':     /* Recenter (and halt) Everything */
407       drawer_center(NOID); break;
408 
409     case 'L':
410       gv_look(retarget(uistate.targetid),FOCUSID);
411       break;
412 
413       /*
414        * r/t/z/f/o apply to the currently selected object unless target specified.
415        */
416     case 'f': s = OBJFLY; goto mote;
417     case 'o': s = OBJORBIT; goto mote;
418     case 'r': s = OBJROTATE; goto mote;
419     case 't': s = OBJTRANSLATE; goto mote;
420     case 'z': s = OBJZOOM; goto mote;
421     case 's': s = OBJSCALE; goto mote;
422 
423     mote:
424       id = retarget(NOID);
425       if (id) gv_ui_target( id, IMMEDIATE);
426       gv_event_mode( s );
427       break;
428 
429     case '?':
430       print_help();
431       break;
432 
433 #ifdef sgi
434     case 'T':   /* NTSC */
435 #endif
436     case 'v':	/* view-related prefix */
437     case 'a':	/* appearance-related prefix */
438     case 'm':	/* metric (euclidean/hyperbolic/spherical) */
439     case 'l':	/* light-related prefix */
440     case 'd':   /* delete */
441     case 'R':   /* renderman */
442     case 'C':	/* color-pick */
443     case 'P':	/* panel show */
444     case 'u':	/* motion style */
445     case ESC:	/* quit prefix */
446       if(keymode != KEYNONE) {
447 	prefixid = retarget(NOID);
448 	if(ISGEOM(prefixid))
449 	  gv_ui_target( prefixid, IMMEDIATE);
450       }
451       prefix = event.dev;
452       goto keepnumber;
453 
454     case SEQ('P','m'):
455     case SEQ('P','g'): s = "main"; goto pickpanel;
456     case SEQ('P','a'): s = "Appearance"; goto pickpanel;
457     case SEQ('P','o'): s = "Obscure"; goto pickpanel;
458     case SEQ('P','l'): s = "Lighting"; goto pickpanel;
459     case SEQ('P','C'): s = "Command"; goto pickpanel;
460     case SEQ('P','c'): s = "Camera"; goto pickpanel;
461     case SEQ('P','t'): s = "Tools"; goto pickpanel;
462     case SEQ('P','f'): s = "Files"; goto pickpanel;
463     case SEQ('P','s'): s = "Save"; goto pickpanel;
464     case SEQ('P','M'): s = "Materials"; goto pickpanel;
465     case SEQ('P','A'): s = "Credits"; goto pickpanel;
466     pickpanel:
467     ui_showpanel(ui_name2panel(s), getint(-1));
468     break;
469     case SEQ('P','P'):
470       ui_manual_browser("pdf");
471       break;
472     case SEQ('P','H'):
473       ui_manual_browser("html");
474       break;
475     case SEQ('P','-'):
476       comm_object("(read command < -)", &CommandOps, NULL, NULL, COMM_LATER);
477       break;
478 
479 
480     case SEQ(ESC,ESC):
481       gv_exit();
482       /*NOTREACHED*/
483 
484     case SEQ('C','f'): k = DRAWER_DIFFUSE; goto pickcolor;
485     case SEQ('C','e'): k = DRAWER_EDGECOLOR; goto pickcolor;
486     case SEQ('C','n'): k = DRAWER_NORMALCOLOR; goto pickcolor;
487     case SEQ('C','b'): k = DRAWER_BBOXCOLOR; goto pickcolor;
488     case SEQ('C','v'):
489     case SEQ('C','B'): k = DRAWER_BACKCOLOR; goto pickcolor;
490     pickcolor:
491     ui_pickcolor( k );
492     break;
493 
494     case SEQ('u','i'): k = DRAWER_INERTIA; goto motstyle;
495     case SEQ('u','c'): k = DRAWER_CONSTRAIN; goto motstyle;
496     case SEQ('u','o'): k = DRAWER_OWNMOTION;
497     motstyle:
498     drawer_int( WORLDGEOM, k, getint(-1) );
499     break;
500 
501     case SEQ('v','+'): 		/* Add camera */
502       { CameraStruct cs;
503 	id = retarget(FOCUSID);
504 	dv = ISCAM(id) ? (DView *)drawer_get_object(id) : NULL;
505 	cs.h = NULL;
506 	cs.cam = dv && dv->cam ? CamCopy(dv->cam, NULL) : NULL;
507 	gv_new_camera(NULL, &cs);
508       }
509       break;
510 
511     case SEQ('v','p'):		/* Projection: orthographic or perspective */
512       id = retarget(FOCUSID);
513       if(!number.has) {
514 	dv = (DView *)drawer_get_object(id);
515 	if(dv) CamGet(dv->cam, CAM_PERSPECTIVE, &number.val);
516 	number.val = !number.val;
517       }
518       drawer_int( id, DRAWER_PROJECTION, number.val );
519       break;
520 
521     case SEQ('v','d'):			/* toggle "Draw other cameras" */
522       id = retarget(FOCUSID);
523       if(!number.has) {
524 	dv = (DView *)drawer_get_object(id);
525 	number.val = dv ? !dv->cameradraw : true;
526       }
527       drawer_int( id, DRAWER_CAMERADRAW, number.val );
528       break;
529 
530     case SEQ('v','D'): /* Toggle dithering */
531       id = retarget(FOCUSID);
532       gv_dither(id, TOGGLE_KEYWORD);
533       break;
534 
535       /* stuff for X11 version */
536     case SEQ('v','h'): /* pick hidden surface removal method: */
537       id = retarget(FOCUSID);
538       dv = (DView *)drawer_get_object(id);
539       if(dv == NULL || dv->mgctx == NULL)
540 	break;
541       if (!number.has) {
542 	mgctxselect(dv->mgctx);
543 	mgctxget(MG_DEPTHSORT, &number.val);
544 	number.val = (number.val+1) % 3;
545       }
546       mgctxset(MG_DEPTHSORT,
547 	       number.val>=0&&number.val<=2 ? number.val : 2, MG_END);
548       gv_redraw(dv->id);
549       ui_maybe_refresh(dv->id);
550       break;
551       /* end of stuff for X11 version */
552 
553     case SEQ('v','x'): /* Toggle/enable/disable cursor */
554       ui_curson( number.has ? number.val : -1 );
555       break;
556 
557     case SEQ('v','b'):
558       tog_ap_flag( id, APF_BACKCULL );
559       break;
560 
561     case SEQ('v','s'):
562       id = retarget(FOCUSID);
563       number.val = !number.val;	/* "1vs" => single-buffered */
564       drawer_int( id, DRAWER_DOUBLEBUFFER, getint(-1) );
565       break;
566 
567       /* For testing software shading */
568     case SEQ('v','~'):
569       id = retarget(FOCUSID);
570       gv_soft_shader(id,
571 		     number.has ? (number.val?ON_KEYWORD:OFF_KEYWORD) : TOGGLE_KEYWORD);
572       break;
573 
574 
575       /* Viewing options */
576     case SEQ('a','c'):
577     case SEQ('v','c'): k = DRAWER_LINE_ZNUDGE; v = 10.; goto setcam;
578     case SEQ('v','v'): k = DRAWER_FOV;  v = 45.; goto setcam;
579     case SEQ('v','n'): k = DRAWER_NEAR; v = .1;	goto setcam;
580     case SEQ('v','f'): k = DRAWER_FAR;  v = 100.; goto setcam;
581     case SEQ('v','l'): k = DRAWER_FOCALLENGTH; v = 3.; goto setcam;
582     setcam:
583     drawer_float( retarget(FOCUSID), k, getreal(v) );
584     break;
585 
586     /* Might add others here, e.g. a viewfinder mode. */
587 
588     /* Metrics / Models */
589     case SEQ('m','e'):
590       gv_space(EUCLIDEAN_KEYWORD);
591       break;
592     case SEQ('m','h'):
593       gv_space(HYPERBOLIC_KEYWORD);
594       break;
595     case SEQ('m','s'):
596       gv_space(SPHERICAL_KEYWORD);
597       break;
598     case SEQ('m','v'):
599       gv_hmodel(retarget(FOCUSID), VIRTUAL_KEYWORD);
600       break;
601     case SEQ('m','p'):
602       gv_hmodel(retarget(FOCUSID), PROJECTIVE_KEYWORD);
603       break;
604     case SEQ('m','c'):
605       gv_hmodel(retarget(FOCUSID), CONFORMALBALL_KEYWORD);
606       break;
607 
608       /* Appearance settings */
609     case SEQ('a','f'): apfl = APF_FACEDRAW; goto togapflag;
610     case SEQ('a','e'): apfl = APF_EDGEDRAW; goto togapflag;
611     case SEQ('a','l'): apfl = APF_SHADELINES; goto togapflag;
612     case SEQ('a','n'): apfl = APF_NORMALDRAW; goto togapflag;
613     case SEQ('a','v'): apfl = APF_EVERT; goto togapflag;
614     case SEQ('a','t'): apfl = APF_TEXTURE; goto togapflag;
615     case SEQ('a','T'): apfl = APF_TRANSP; goto togapflag;
616     case SEQ('a','V'): apfl = APF_VECTDRAW; goto togapflag;
617     case SEQ('a','C'): apfl = APF_CONCAVE; goto togapflag;
618     case SEQ('a','q'): apfl = APF_TXMIPMAP|APF_TXMIPINTERP|APF_TXLINEAR; goto togapflag;
619     togapflag:
620     tog_ap_flag( id, apfl );
621     break;
622     case SEQ('a','x'): drawer_set_ap( id, NULL, NULL ); break;
623     case SEQ('a','o'): gv_ap_override( getint( !uistate.apoverride ) ); break;
624 
625 
626     case SEQ('a','b'): /* Bounding box drawing */
627       if(!number.has) {
628 	DGeom *dg = (DGeom *)drawer_get_object( id );
629 	if(dg) number.val = !dg->bboxdraw;
630       }
631       drawer_int(id, DRAWER_BBOXDRAW, number.val);
632       break;
633 
634     case SEQ('a','s'):	/* Shading */
635       if(!number.has) {
636 	ap = drawer_get_ap(id);
637 	ApGet(ap, AP_SHADING, &number.val);
638 	ApDelete(ap);
639 	number.val++;
640       }
641       drawer_int(id, DRAWER_SHADING, number.val % 5);
642       break;
643 
644     case SEQ('a','w'):	/* line width */
645       if(!number.has) {
646 	ap = drawer_get_ap(id);
647 	ApGet(ap, AP_LINEWIDTH, &number.val);
648 	ApDelete(ap);
649 	number.val = (number.val > 1) ? 1 : 2;
650       }
651       drawer_int(id, DRAWER_LINEWIDTH, number.val);
652       break;
653 
654       /* Scale normals */
655     case SEQ('a','h'): drawer_float(id, DRAWER_NORMSCALE, getreal(1.0)); break;
656 
657       /* Patch dicing */
658     case SEQ('a','d'): drawer_int( id, DRAWER_BEZDICE, number.val ); break;
659 
660       /* hyperbolic sphere at infinity */
661     case SEQ('a', 'i'): drawer_int( retarget(FOCUSID), DRAWER_HSPHERE,
662 				    getint(-1) );
663       break;
664 
665       /* Delete */
666     case SEQ('d','d'): gv_delete(uistate.targetid); break;
667 
668       /* NTSC */
669 #ifdef sgi
670     case SEQ('T','V'): ntsc(getint(-1)); break;
671 #endif
672 
673       /* Timing -- ctrl-T
674        * ^T : print accumulated timing status now
675        * <nnn>^T : print timing status now and every <nnn> main-loop cycles
676        * -^T : quit timing
677        */
678     case 'T'&0x1f:
679       timing( number.has<0 ? 0 : number.has ? number.val : 9999999 );
680       break;
681 
682       /* Edit Lights */
683     case SEQ('l','e'):
684       if (!(uistate.lights_shown)) light_edit_mode(1);
685       else gv_event_mode( LIGHTEDIT );
686       break;
687 
688       /* Toggle Show Lights */
689     case SEQ('l','s'): light_edit_mode(2); break;
690 
691       /*
692        * All R* commands moved to rman.c - slevy.
693        */
694     default:
695       err = EOF;
696       if(prefix == 'R') {
697 	rman_do(event.dev,number.has,number.val);
698 	break;
699       } else if(prefix != 0) {		/* No such command? */
700 	prefix = 0;
701 	goto rescan;		/* Try same char without prefix */
702       }
703       keymode = KEYNONE;
704     }
705     number.has = number.expon = onum.has = onum.expon = 0;
706     prefix = 0;
707     prefixid = NOID;
708     ui_keyboard(err);			/* 0 = OK, EOF = -1 = error */
709   keepnumber:
710     keymode = KEYNONE;
711   keepmode:
712     ;
713   }
714 
715   return Lt;
716 }
717 
718 /*
719  * Interpret a g[N] or c[N] prefix; return the id.
720  */
721 static int
retarget(int defindex)722 retarget(int defindex)
723 {
724   int t;
725   static int allid[2] = { ALLGEOMS, ALLCAMS };
726   static char ch[2] = { 'g', 'c' };
727   char code[12];
728 
729   if(keymode == KEYNONE) {
730     if(number.expon && !number.has) {  /* a "." prefix, sans number */
731       number.expon = 0;
732       return TARGETID;
733     }
734     return prefixid != NOID ?
735       prefixid : defindex;	/* No prefix, or just numeric */
736   }
737 
738   sprintf(code, "%c%d", ch[keymode], number.val);
739   if(number.has > 0) t = drawer_idbyname(code);
740   else if(number.has < 0) t = allid[keymode];
741   else t = (keymode == KEYGEOM) ? WORLDGEOM
742     : FOCUSID;
743   number = onum;
744   onum.has = onum.expon = 0;
745   keymode = KEYNONE;
746   return t;
747 }
748 
749 /* Return current number if any; otherwise return given default value. */
750 
751 static float
getreal(float defval)752 getreal(float defval)
753 {
754   float v = number.has * number.val;
755 
756   if(!number.has) return defval;
757   while(--number.expon > 0)
758     v *= 0.1;
759   return v;
760 }
761 
762 static int
getint(int defaultvalue)763 getint(int defaultvalue)
764 {
765   return number.has ? number.val*number.has : defaultvalue;
766 }
767 
768 #if 0
769 static int
770 toggle(int val)
771 {
772   return number.has ? (number.has = number.expon = 0, number.val) : !val;
773 }
774 #endif
775 
776 static void
tog_ap_flag(int id,int flagbit)777 tog_ap_flag( int id, int flagbit )
778 {
779   ApStruct as;
780   int val;
781 
782   memset(&as, 0, sizeof(as));
783 
784   if(number.has) {
785     val = number.val;
786   } else {
787     as.ap = drawer_get_ap(id);
788     val = as.ap ? !(as.ap->flag & flagbit) : 1;
789   }
790   as.ap = ApCreate(val ? AP_DO : AP_DONT, flagbit,
791 		   AP_OVERRIDE, uistate.apoverride & flagbit, AP_END);
792   gv_merge_ap(id, &as);
793   ApDelete(as.ap);
794 }
795 
796 
797 
798 /* NB - I've put in a total hack to avoid calling gvpick more than
799  * once - I think it's pretty stable (so when it stays in for years
800  * and years it might(?) keep working!) -cf 10/29/92 */
801 static int
view_pick(DView * dv,int x,int y,Pick * pick)802 view_pick( DView *dv, int x, int y, Pick *pick )
803 {
804   Transform V, T, Tnet, Tt, Tmodel, Tnorm, oldTw, Tworld;
805   int i;
806   int chosen = NOID;
807   float xpick, ypick;
808   DGeom *dg;
809   Appearance *ap;
810   WnPosition wp;
811 
812   if(dv == NULL)
813     return NOID;
814 
815   if(dv->stereo == NO_KEYWORD) {
816     wp = drawerstate.winpos;
817     mousemap(x, y, &xpick, &ypick, &wp);
818   } else {
819     /* Map screen -> view position in a stereo window.
820      */
821     for(i = 0; i < 2; i++) {
822       wp.xmin = drawerstate.winpos.xmin + dv->vp[i].xmin;
823       wp.xmax = wp.xmin + dv->vp[i].xmax - dv->vp[i].xmin;
824       wp.ymin = drawerstate.winpos.ymin + dv->vp[i].ymin;
825       wp.ymax = wp.ymin + dv->vp[i].ymax - dv->vp[i].ymin;
826       mousemap(x, y, &xpick, &ypick, &wp);
827       if(fabs(xpick) <= 1 && fabs(ypick) <= 1)
828 	break;
829     }
830   }
831 
832   { /* Hacks for setting up transforms so INST ... location / origin works.
833      * This'll be unnecessary if we switch over to using an mg "pick" device.
834      * slevy 96.10.26.
835      */
836     Transform Tc2n, Tw2n, Ts2n;
837     CamViewProjection( dv->cam, Tc2n );
838     CamView( dv->cam, Tw2n );
839     TmTranslate(Ts2n, -1.0, -1.0, 0.0);
840     CtmScale(Ts2n, 2.0/(wp.xmax-wp.xmin+1), 2.0/(wp.ymax-wp.ymin+1), 1.0);
841     PickSet( pick, PA_TC2N, Tc2n, PA_TW2N, Tw2n, PA_TS2N, Ts2n, PA_END );
842   }
843 
844   if (drawerstate.NDim > 0) {
845     TransformN *V, *W, *Tnet, *Tmodel, *Tworld;
846 
847     V = drawer_ND_CamView(dv, NULL);
848 
849     if (dv->Item != drawerstate.universe) {
850       /* Picking in a window with a dedicated Scene */
851       /* We yield results in the Scene's coordinate system */
852       if (GeomMousePick(dv->Item, pick, (Appearance *)NULL,
853 			NULL, V, dv->NDPerm, xpick, ypick)) {
854 	chosen = dv->id;
855       }
856       return chosen;
857     }
858 
859     /* Picking in the real world */
860     Tworld = drawer_get_ND_transform(WORLDGEOM, UNIVERSE);
861     TmNConcat(Tworld, V, V); /* world -> screen */
862     TmNDelete(Tworld);
863 
864     /*  Now V contains the world -> screen projection */
865     LOOPSOMEGEOMS(i,dg,ORDINARY) {
866       if (dg->pickable) {
867 	Geom *g = NULL;
868 	int id = GEOMID(i);
869 
870 	if (dg->Lgeom) {
871 	  Tmodel = drawer_get_ND_transform(id, WORLDGEOM);
872 	  Tnet = TmNConcat(Tmodel, V, NULL); /* Now Tnet geom -> screen */
873 	  GeomGet(dg->Lgeom, CR_GEOM, &g);
874 	  ap = drawer_get_ap(dg->id);
875 	  if (GeomMousePick(g, pick, ap, NULL,
876 			    Tnet, dv->NDPerm, xpick, ypick)) {
877 	    chosen = id;
878 	    /* Arrange for things to be in world coords not Dgeom coords. */
879 	    pick->TwN = TmNConcat(pick->TwN, Tmodel, pick->TwN);
880 	    W = drawer_get_ND_transform(WORLDGEOM, id);
881 	    pick->TselfN = TmNConcat(pick->TwN, W, pick->TselfN);
882 	    TmNDelete(W);
883 	  }
884 	  TmNDelete(Tmodel);
885 	  TmNDelete(Tnet);
886 	  ApDelete(ap);
887 	}
888       }
889     }
890 
891     TmNDelete(V);
892 
893     return chosen;
894   }
895 
896   CamView( dv->cam, V );	/* V = camera-to-screen matrix
897 				 * cH: wrong. V = universe-to-screen matrix
898 				 */
899 
900   if(dv->Item != drawerstate.universe) {
901     /* Picking in a window with a dedicated Scene */
902     /* We yield results in the Scene's coordinate system */
903     /* Is this really correct? Why should we call GeomPosition() here?
904      * dv->Item is just a normal geometry, only by chance a
905      * single-element INST.
906      */
907 #if 0
908     GeomPosition( dv->Item, T );
909     TmConcat(T,V, T);		/* T = Scene to screen projection */
910     if(GeomMousePick( dv->Item, pick, (Appearance *)NULL, T, xpick, ypick )) {
911       chosen = dv->id;
912     }
913 #else
914     if (GeomMousePick( dv->Item, pick, (Appearance *)NULL,
915 		       V, NULL, NULL, xpick, ypick )) {
916       chosen = dv->id;
917     }
918 #endif
919     return chosen;
920   }
921 
922   /* Picking in the real world */
923   GeomPosition(drawerstate.world, Tworld); /* world -> universe */
924   TmConcat(Tworld, V, T); /* world -> screen */
925   /*
926    * We now assume the complete screen -> DGeom transform is in T.
927    * This is true only if we have just a single level of DGeom's in the world.
928    *
929    * cH: this is wrong, T is the World -> screen projection, not the
930    * other way round. (Transforms operate from the right!).
931    */
932   LOOPSOMEGEOMS(i,dg,ORDINARY) {
933     if (dg->pickable) {
934       Geom *g = NULL;
935       int id = GEOMID(i);
936 
937       if (dg->Lgeom) {
938 	GeomPosition( dg->Item, Tmodel );
939 	GeomPosition( dg->Inorm, Tnorm );
940 	TmConcat( Tnorm, Tmodel, Tt );
941 	TmConcat( Tt, T, Tnet ); /* Now Tnet = complete geom-to-screen proj'n */
942 	GeomGet( dg->Lgeom, CR_GEOM, &g );
943 	ap = drawer_get_ap(dg->id);
944 	if (GeomMousePick( g, pick, ap, Tnet, NULL, NULL, xpick, ypick )) {
945 	  chosen = id;
946 	  /* We remember oldTw to print out info below for debugging only */
947 	  TmCopy(pick->Tw, oldTw);
948 	  /* This is necessary!  Arranges for things to be in world
949 	   * coords not Dgeom coords. Tt is the dgeom-to-world
950 	   * transform, Tw is (more or less) screen -> dgeom
951 	   */
952 	  TmConcat(pick->Tw, Tt, pick->Tw);
953 	  drawer_get_transform(WORLDGEOM, pick->Tself, id);
954 	  TmConcat(pick->Tw, pick->Tself, pick->Tself);
955 	}
956 	ApDelete(ap);
957       }
958     }
959   }
960 
961   /* Ok, everything below is just debugging stuff */
962   if (chosen == NOID) {
963     /*    printf("Picked nothing.\n"); */
964   } else {
965     /*    printf("Picked dgeom #%d\n", INDEXOF(chosen)); */
966 
967     /* pick->got is in mouse coords.
968        wgot is world coords.
969        old world is really dgeom coords. (maybe...)
970        got is raw object coords. (the kind of numbers in geom data file!)
971 
972     */
973     if (pick && getenv("VERBOSE_PICK")) {
974       Point3 got, v, e[2], wgot, wv, we[2], owgot, owv, owe[2];
975 
976       Pt3Transform(pick->Tmirp, &(pick->got), &got);
977       Pt3Transform(pick->Tw, &(pick->got), &wgot);
978       Pt3Transform(oldTw, &(pick->got), &owgot);
979 
980       printf("pick->\n");
981       printf("  got = (%f %f %f)\n",
982 	     pick->got.x, pick->got.y, pick->got.z);
983       if (pick->found&PW_VERT)
984 	printf("    v = (%f %f %f)\n",
985 	       pick->v.x, pick->v.y, pick->v.z);
986       if (pick->found&PW_EDGE) {
987 	printf(" e[0] = (%f %f %f)\n",
988 	       pick->e[0].x, pick->e[0].y, pick->e[0].z);
989 	printf(" e[1] = (%f %f %f)\n",
990 	       pick->e[1].x, pick->e[1].y, pick->e[1].z);
991       }
992 
993       printf("Transformed pick [raw]->\n");
994       printf("  got = (%f %f %f)\n", got.x, got.y, got.z);
995       if (pick->found&PW_VERT) {
996 	HPt3TransPt3(pick->Tmirp, &(pick->v), &v);
997 	printf("    v = (%f %f %f)\n", v.x, v.y, v.z);
998       }
999       if (pick->found&PW_EDGE) {
1000 	HPt3TransPt3(pick->Tmirp, &(pick->e[0]), &(e[0]));
1001 	HPt3TransPt3(pick->Tmirp, &(pick->e[1]), &(e[1]));
1002 	printf(" e[0] = (%f %f %f)\n", e[0].x, e[0].y, e[0].z);
1003 	printf(" e[1] = (%f %f %f)\n", e[1].x, e[1].y, e[1].z);
1004       }
1005 
1006       printf("Transformed pick [old world]->\n");
1007       printf("  got = (%f %f %f)\n", owgot.x, owgot.y, owgot.z);
1008       if (pick->found&PW_VERT) {
1009 	HPt3TransPt3(oldTw, &(pick->v), &owv);
1010 	printf("    v = (%f %f %f)\n", owv.x, owv.y, owv.z);
1011       }
1012       if (pick->found&PW_EDGE) {
1013 	HPt3TransPt3(oldTw, &(pick->e[0]), &(owe[0]));
1014 	HPt3TransPt3(oldTw, &(pick->e[1]), &(owe[1]));
1015 	printf(" e[0] = (%f %f %f)\n", owe[0].x, owe[0].y, owe[0].z);
1016 	printf(" e[1] = (%f %f %f)\n", owe[1].x, owe[1].y, owe[1].z);
1017 
1018 	printf("Transformed pick [world]->\n");
1019 	printf("  got = (%f %f %f)\n", wgot.x, wgot.y, wgot.z);
1020 	if (pick->found&PW_VERT) {
1021 	  HPt3TransPt3(pick->Tw, &(pick->v), &wv);
1022 	  printf("    v = (%f %f %f)\n", wv.x, wv.y, wv.z);
1023 	}
1024 	if (pick->found&PW_EDGE) {
1025 	  HPt3TransPt3(pick->Tw, &(pick->e[0]), &(we[0]));
1026 	  HPt3TransPt3(pick->Tw, &(pick->e[1]), &(we[1]));
1027 	  printf(" e[0] = (%f %f %f)\n", we[0].x, we[0].y, we[0].z);
1028 	  printf(" e[1] = (%f %f %f)\n", we[1].x, we[1].y, we[1].z);
1029 	}
1030 
1031 
1032       }
1033     }
1034   }
1035   return chosen;
1036 }
1037 
1038 LDEFINE(rawpick, LINT,
1039 	"(rawpick CAMID X Y)\n\
1040 	Process a pick event in camera CAMID at location (X,Y) given in\n\
1041 	integer pixel coordinates.  This is a low-level procedure not\n\
1042 	intended for external use.")
1043 {
1044   int pickedid, id, x, y;
1045   Pick *pick;
1046 
1047   LDECLARE(("rawpick", LBEGIN,
1048 	    LID, &id,
1049 	    LINT, &x,
1050 	    LINT, &y,
1051 	    LEND));
1052   if (TYPEOF(id) != T_CAM) {
1053     fprintf(stderr, "rawpick: first arg must be a camera id\n");
1054     return Lnil;
1055   }
1056 
1057   pick = PickSet(NULL, PA_WANT, PW_EDGE|PW_VERT|PW_FACE, PA_END);
1058   pickedid= view_pick( (DView *)drawer_get_object(id), x, y, pick );
1059 
1060   if (pickedid != NOID) {
1061     emit_pick(pickedid, pick);
1062   }
1063   PickDelete(pick);
1064   return LNew(LINT, &pickedid);
1065 }
1066 
1067 void
emit_pick(int pickedid,Pick * pick)1068 emit_pick(int pickedid, Pick *pick)
1069 {
1070   /* Variables for total hack */
1071   vvec done;
1072   char donebits[512];
1073 
1074   LInterest *interest = LInterestList("pick");
1075 
1076   VVINIT(done, char, 128);
1077   if(interest) {
1078     vvuse(&done, donebits, COUNT(donebits));
1079     vvzero(&done);
1080   }
1081 
1082 #define DONEID(id) *VVINDEX(done, char, id-CAMID(-20))
1083 
1084   for ( ; interest != NULL; interest = interest->next) {
1085     int coordsysid;
1086 
1087     /* extract the coord system to use from the interest filter;
1088        if none given, use world */
1089     if (interest->filter
1090 	&& interest->filter->car
1091 	&& (LFILTERVAL(interest->filter->car)->flag == VAL)) {
1092       if (!LFROMOBJ(LID)(LFILTERVAL(interest->filter->car)->value,
1093 			 &coordsysid)) {
1094 	OOGLError(0,"emit_pick: bad coord sys filter type");
1095 	continue;
1096       }
1097     } else {
1098       coordsysid = WORLDGEOM;
1099     }
1100 
1101     if (drawerstate.NDim > 0) {
1102       /* In an ND-context, we report the 3d-quantities picked in the
1103        * 3d sub-space of the camera where the pick occurred. The
1104        * interested parties still have access to the ND-co-ordinates
1105        * by means of the vertex/edge/face indices.
1106        *
1107        * We use TmNMap() to map the 3d vertices to the full Nd-space.
1108        */
1109       int dim = drawerstate.NDim;
1110       TransformN *T, *tmp;
1111       HPointN *got = NULL;
1112       HPointN *v = NULL;
1113       VARARRAY(e, HPtNCoord, 2*dim);
1114       VARARRAY(f, HPtNCoord, pick->found & PW_FACE ? pick->fn * dim : 0);
1115       int gn, vn, vi, en, ei[2], ein, fn, fi;
1116 
1117       /*  T = transform converting to the coord system of coordsysid */
1118       /* This section does the setup for the total hack */
1119 
1120       /* Total hack gigantic if statement */
1121       if (!DONEID(coordsysid)) {
1122 	DONEID(coordsysid) = 1;
1123 	switch(coordsysid) {
1124 	case WORLDGEOM:
1125 	  T = TmNMap(pick->TwN, pick->axes, NULL);
1126 	  break;
1127 	case PRIMITIVE:
1128 	  T = TmNMap(pick->TmirpN, pick->axes, NULL);
1129 	  break;
1130 	case SELF:
1131 	  T = TmNMap(pick->TselfN, pick->axes, NULL);
1132 	  break;
1133 	default:
1134 	  tmp = drawer_get_ND_transform(WORLDGEOM, coordsysid);
1135 	  T = TmNConcat(pick->TwN, tmp, NULL);
1136 	  TmNDelete(tmp);
1137 	  TmNMap(T, pick->axes, T);
1138 	  break;
1139 	}
1140 
1141 	if (pickedid != NOID) {
1142 	  HPoint3 gothpt4;
1143 
1144 	  Pt3ToHPt3(&pick->got, &gothpt4, 1);
1145 	  got = HPt3NTransform(T, &gothpt4, NULL);
1146 	  gn = got->dim;
1147 #if 1
1148 	  {
1149 	    HPointN *gotn;
1150 
1151 	    gotn = HPt3ToHPtN(&gothpt4, pick->axes, NULL);
1152 	    HPtNTransform(pick->TwN, gotn, gotn);
1153 	    HPtNDelete(gotn);
1154 	  }
1155 #endif
1156 	} else {
1157 	  got = NULL;
1158 	  gn = 0;
1159 	}
1160 
1161 	if (pick->found & PW_VERT) {
1162 	  v = HPt3NTransform(T, &pick->v, NULL);
1163 	  vn = v->dim;
1164 	  vi = pick->vi;
1165 	} else {
1166 	  v = NULL;
1167 	  vn = 0;
1168 	  vi = -1;
1169 	}
1170 
1171 	if (pick->found & PW_EDGE) {
1172 	  HPointN tmp;
1173 
1174 	  tmp.dim   = T->odim;
1175 	  tmp.flags = 0;
1176 
1177 	  tmp.v = e+0;   HPt3NTransform(T, &pick->e[0], &tmp);
1178 	  tmp.v = e+dim; HPt3NTransform(T, &pick->e[1], &tmp);
1179 	  en = 2*dim;
1180 	  ei[0] = pick->ei[0];
1181 	  ei[1] = pick->ei[1];
1182 	  ein = 2;
1183 	} else {
1184 	  en = 0;
1185 	  ein = 0;
1186 	}
1187 	if (pick->found & PW_FACE) {
1188 	  HPointN tmp;
1189 	  int i;
1190 
1191 	  tmp.dim   = T->odim;
1192 	  tmp.flags = 0;
1193 	  for (i = 0; i < pick->fn; i++) {
1194 	    tmp.v = &f[i*dim];
1195 	    HPt3NTransform(T, pick->f+i, &tmp);
1196 	  }
1197 	  fi = pick->fi;
1198 	  fn = pick->fn * dim;
1199 	} else {
1200 	  fn = 0;
1201 	  fi = -1;
1202 	}
1203 
1204 	/* Cause of total hack.
1205 	 * This CANNOT be called once for every interested party - otherwise
1206 	 * every interested party will hear about it numerous times. */
1207 	gv_pick(coordsysid, pickedid,
1208 		got ? got->v : NULL, gn,
1209 		v ? v->v : NULL, vn,
1210 		e, en,
1211 		f, fn,
1212 		VVEC(pick->gpath, int), VVCOUNT(pick->gpath),
1213 		vi,
1214 		ei, ein,
1215 		fi);
1216 
1217 	HPtNDelete(got);
1218 	HPtNDelete(v);
1219 	TmNDelete(T);
1220 
1221       } /* End of total hack if statement */
1222 
1223     } else {
1224       /*  T = transform converting to the coord system of coordsysid */
1225       /* This section does the setup for the total hack */
1226       Transform T;
1227       HPoint3 got;
1228       HPoint3 v, e[2];
1229       HPoint3 *f;
1230       int gn, vn, vi, en, ei[2], ein, fn, fi;
1231 
1232       /* Total hack gigantic if statement */
1233       if (!DONEID(coordsysid)) {
1234 	DONEID(coordsysid) = 1;
1235 	switch(coordsysid) {
1236 	case WORLDGEOM:
1237 	  TmCopy(pick->Tw, T); break;
1238 	case PRIMITIVE:
1239 	  TmCopy(pick->Tmirp, T); break;
1240 	case SELF:
1241 	  TmCopy(pick->Tself, T); break;
1242 	default:
1243 	  drawer_get_transform(WORLDGEOM, T, coordsysid);
1244 	  TmConcat(pick->Tw, T, T);
1245 	  break;
1246 	}
1247 
1248 	if (pickedid != NOID) {
1249 	  Pt3Transform(T, &pick->got, HPoint3Point3(&got));
1250 	  got.w = 1;
1251 	  gn = 4;
1252 	} else {
1253 	  gn = 0;
1254 	}
1255 
1256 	if (pick->found & PW_VERT) {
1257 	  HPt3Transform(T, &(pick->v), &v);
1258 	  vn = 4;
1259 	  vi = pick->vi;
1260 	} else {
1261 	  vn = 0;
1262 	  vi = -1;
1263 	}
1264 
1265 	if (pick->found & PW_EDGE) {
1266 	  HPt3TransformN(T, pick->e, &e[0], 2);
1267 	  en = 8;
1268 	  ei[0] = pick->ei[0];
1269 	  ei[1] = pick->ei[1];
1270 	  ein = 2;
1271 	} else {
1272 	  en = 0;
1273 	  ein = 0;
1274 	}
1275 	if (pick->found & PW_FACE) {
1276 	  f = OOGLNewNE(HPoint3, pick->fn, "rawpick");
1277 	  HPt3TransformN(T, pick->f, f, pick->fn);
1278 	  fi = pick->fi;
1279 	  fn = pick->fn * 4;
1280 	} else {
1281 	  f = NULL;
1282 	  fn = 0;
1283 	  fi = -1;
1284 	}
1285 
1286 	/* Cause of total hack.
1287 	 * This CANNOT be called once for every interested party - otherwise
1288 	 * every interested party will hear about it numerous times. */
1289 	gv_pick(coordsysid, pickedid,
1290 		HPoint3Data(&got), gn,
1291 		&v.x, vn,
1292 		&e[0].x, en,
1293 		(float *)f, fn, 	/* f, fn, */
1294 		VVEC(pick->gpath, int), VVCOUNT(pick->gpath),
1295 		vi,
1296 		ei, ein,
1297 		fi);
1298 
1299 	if (f != NULL) OOGLFree(f);
1300 
1301       } /* End of total hack if statement */
1302 
1303     } /* End of 3d case */
1304   }
1305   vvfree(&done);
1306 }
1307 
1308 LDEFINE(pick, LVOID,
1309 	"(pick COORDSYS GEOMID G V E F P VI EI FI)\n"
1310 	"The pick command is executed internally in response to pick\n"
1311 	"events (right mouse double click).\n"
1312 	"\n\n"
1313 	"COORDSYS = coordinate system in which coordinates of the following\n"
1314 	"arguments are specified. This can be:"
1315 	"\n\n\tworld: world coord sys"
1316 	"\n\n\tself:  coord sys of the picked geom (GEOMID)"
1317 	"\n\n\tprimitive: coord sys of the actual primitive within"
1318 	"\n\n\t\tthe picked geom where the pick occurred."
1319 	"\n\n\n\n"
1320 	"GEOMID = id of picked geom"
1321 	"\n\n\n\n"
1322 	"G = picked point (actual intersection of pick ray with object)"
1323 	"\n\n\n\n"
1324 	"V = picked vertex, if any"
1325 	"\n\n\n\n"
1326 	"E = picked edge, if any"
1327 	"\n\n\n\n"
1328 	"F = picked face"
1329 	"\n\n\n\n"
1330 	"P = path to picked primitive [0 or more]"
1331 	"\n\n\n\n"
1332 	"VI = index of picked vertex in primitive"
1333 	"\n\n\n\n"
1334 	"EI = list of indices of endpoints of picked edge, if any"
1335 	"\n\n\n\n"
1336 	"FI = index of picked face"
1337 	"\n\n\n\n"
1338 	"External modules can find out about pick events by registering\n"
1339 	"interest in calls to \"pick\" via the \"interest\" command."
1340 	"\n\n\n\n"
1341 	"In the ND-viewing context the co-ordinates are actually ND-points.\n"
1342 	"They correspond to the 3D points of the pick relative to the\n"
1343 	"sub-space defined by the viewport of the camera where the pick\n"
1344 	"occurred. The co-ordinates are then padded with zeroes and\n"
1345 	"transformed back to the co-ordinate system defined by\n"
1346 	"\"COORDSYS\".")
1347 {
1348   float *got = NULL, *v = NULL, *e = NULL, *f = NULL;
1349   int vi, ei[2], fi, *p = NULL;
1350   int gn, vn, en, fn, pn;
1351   int ein = 2;
1352   int id, coordsys;
1353 
1354   /* NOTE: If you change the lisp syntax of this function (which you
1355      shouldn't do), you must also update the DEFPICKFUNC macro in the
1356      file "pickfunc.h", which external modules use. */
1357   LDECLARE(("pick", LBEGIN,
1358 	    LID, &coordsys,
1359 	    LID, &id,
1360 	    LHOLD, LVARARRAY, LFLOAT, &got, &gn,
1361 	    LHOLD, LVARARRAY, LFLOAT, &v, &vn,
1362 	    LHOLD, LVARARRAY, LFLOAT, &e, &en,
1363 	    LHOLD, LVARARRAY, LFLOAT, &f, &fn,
1364 	    LHOLD, LVARARRAY, LINT, &p, &pn,
1365 	    LINT, &vi,
1366 	    LHOLD, LARRAY, LINT, ei, &ein,
1367 	    LINT, &fi,
1368 	    LEND));
1369 
1370   if (got) OOGLFree(got);
1371   if (v) OOGLFree(v);
1372   if (e) OOGLFree(e);
1373   if (f) OOGLFree(f);
1374   if (p) OOGLFree(p);
1375 
1376   return Lt;
1377 }
1378 
1379 /*****************************************************************************/
1380 
1381 LDEFINE(event_keys, LVOID,
1382 	"(event-keys {on|off})\n\
1383          Turn keyboard events on or off to enable/disable keyboard shortcuts.")
1384 {
1385   int on;
1386 
1387   LDECLARE(("event-keys", LBEGIN,
1388 	    LKEYWORD, &on,
1389 	    LEND));
1390 
1391   if (on == ON_KEYWORD)
1392     keyshorts = 1;
1393   else
1394     if (on == OFF_KEYWORD)
1395       keyshorts = 0;
1396     else {
1397       OOGLError(0, "event-keys: expected \"on\" or \"off\" keyword");
1398       return Lnil;
1399     }
1400 
1401   return Lt;
1402 
1403 }
1404 
1405 /*****************************************************************************/
1406 
1407 LDEFINE(event_pick, LVOID,
1408 	"(event-pick {on|off})\n\
1409          Turn picking on or off.")
1410 {
1411   int on;
1412 
1413   LDECLARE(("event-pick", LBEGIN,
1414 	    LKEYWORD, &on,
1415 	    LEND));
1416 
1417   if (on == ON_KEYWORD)
1418     pickon = 1;
1419   else
1420     if (on == OFF_KEYWORD)
1421       pickon = 0;
1422     else {
1423       OOGLError(0, "event-pick: expected \"on\" or \"off\" keyword");
1424       return Lnil;
1425     }
1426 
1427   return Lt;
1428 }
1429 
1430 /*****************************************************************************/
1431 
1432 LDEFINE(dither, LVOID,
1433         "(dither  CAM-ID {on|off|toggle})\n\
1434          Turn dithering on or off in that camera.")
1435 {
1436   DView *dv;
1437   int id, dither, i, on = -1;
1438 
1439   LDECLARE(("dither", LBEGIN,
1440 	    LID, &id,
1441 	    LOPTIONAL,
1442 	    LKEYWORD, &on,
1443 	    LEND));
1444 
MAYBE_LOOP(id,i,T_CAM,DView,dv)1445   MAYBE_LOOP(id, i, T_CAM, DView, dv) {
1446     if (dv->mgctx) {
1447       mgctxselect(dv->mgctx);
1448       mgctxget(MG_DITHER, &dither);
1449     }
1450     if (on == TOGGLE_KEYWORD)
1451       dither = !dither;
1452     else
1453       if (on == ON_KEYWORD)
1454 	dither = 1;
1455       else
1456 	if (on == OFF_KEYWORD)
1457 	  dither = 0;
1458 	else {
1459 	  OOGLError(0, "dither: expected \"on\", \"off\" or \"toggle\" keyword");
1460 	  return Lnil;
1461 	}
1462 
1463     if (dv->mgctx) {
1464       mgctxset(MG_DITHER, dither, MG_END);
1465       gv_redraw(dv->id);
1466     }
1467     ui_maybe_refresh(dv->id);
1468   }
1469   return Lt;
1470 
1471 }
1472 
1473 /*****************************************************************************/
1474 
1475 /*
1476  * Local Variables: ***
1477  * c-basic-offset: 2 ***
1478  * End: ***
1479  */
1480