ftp.nice.ch/pub/next/graphics/3d/geomview.1.4.1.s.tar.gz#/Geomview/src/bin/geomview/common/event.c

This is event.c in view mode; [Download] [Up]

/* Copyright (c) 1992 The Geometry Center; University of Minnesota
   1300 South Second Street;  Minneapolis, MN  55454, USA;
   
This file is part of geomview/OOGL. geomview/OOGL is free software;
you can redistribute it and/or modify it only under the terms given in
the file COPYING, which you should have received along with this file.
This and other related software may be obtained via anonymous ftp from
geom.umn.edu; email: software@geom.umn.edu. */

/* Authors: Stuart Levy, Tamara Munzner, Mark Phillips */

#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>

#include "mg.h"
#include "drawer.h"
#include "event.h"
#include "lang.h"
#include "ui.h"
#include "comm.h"
#include "pickP.h"
#include "transform.h"
#include "streampool.h"
#include "lights.h"
#include "rman.h"

#define DOUBLECLICKTIME 333	/* millisecs between double clicks */

EventState estate;	/* External motion state */

struct button button;	/* Shift, etc. button state */

void emit_pick(int pickedid, Pick *pick);
static int view_pick( DView *dv, int x, int y, Pick *pick );

#define	ESC	'\033'

static int hasnumber = 0;
static int number;
static int expon;
static float getreal(float);
static int toggle(int val);
static void tog_ap_flag(int id, int flagbit);
static int retarget(int defaultid);

#define	SEQ(prefix, ch)  ((prefix)<<8 | (ch))

static enum { KEYGEOM=0, KEYCAM=1, KEYNONE=2 } keymode = KEYNONE;
static int prefix = 0;

static char Help[] = "\
Keyboard commands apply while cursor is in any graphics window and most \n\
control panels. Most commands allow one of the following selection prefixes \n\
(if none is provided the command applies to the current object): \n\
   g  world geom	g#  #'th geom	g*  All geoms\n\
   c  current camera	c#  #'th camera	c*  All cameras\n\
Many allow a numeric prefix:  if none they toggle or reset current value.\n\
Appearance:\n\
 Draw:		     Shading:		Other:\n\
  af  Faces		0as Constant	 av  eVert normals: always face viewer\n\
  ae  Edges		1as Flat	#aw  Line Width (pixels)\n\
  an  Normals		2as Smooth	#ac  edges Closer than faces(try 5-100)\n\
  ab  Bounding Boxes	3as Smooth, non-lighted\n\
  aV  Vectors		aT  allow transparency\n\
 Color:\n\
  Cf Ce Cn Cb CB   face/edge/normal/bbox/backgnd\n\
Motions:				      Viewing:\n\
  r rotate	   [ Leftmouse=X-Y plane,	0vp Orthographic view\n"
#ifdef NeXT
"  t translate	     Alt-Left=Z axis,		1vp Perspective view\n"
#else
"  t translate	     Middle=Z axis,		1vp Perspective view\n"
#endif
"  z zoom FOV	     Shift=slow motion,		 vd Draw other views' cameras\n\
  f fly		     in r/t modes.      ]	#vv field of View\n\
  o orbit           [Left=steer, Middle=speed ]	#vn near clip distance\n\
  s scale					#vf far clip distance\n\
  w/W recenter/all				 v+ add new camera\n\
  h/H halt/halt all				 vx cursor on/off\n\
  @  select center of motion (e.g. g3@)		 vb backfacing poly cull on/off\n\
						#vl focal length\n\
  L  Look At object				 v~ Software shading on/off\n\
show Panel:	Pm Pa Pl Po	main/appearance/lighting/obscure\n\
		Pt Pc PC Pf	tools/cameras/Commands/file-browser\n\
		Ps P-		saving/read commands from tty\n\
Lights:  ls le		Show lights / Edit Lights\n\
Metric:  me mh ms  	Euclidean Hyperbolic Spherical\n\
Model:   mv mp mc	Virtual Projective Conformal\n\
Other:\n\
  N normalization < Pf  load geom/command file\n\
   0N none	  > Ps  save something to file	ui  motion has inertia\n\
   1N each	  TV	NTSC mode toggle	uc  constrained (X/Y) motion\n\
   2N all	  				uo  motion in Own coord system\n\
  Rightmouse-doubleclick  pick as current target object\n\
  Shift-Rightmouse        pick interest (center) point\n"
#ifdef NeXT
"  Alt-Leftmouse is synonym for Rightmouse.\n"
#endif
;

void 
print_help()
{
  printf("\n%s", Help);
  rman_do('?', 0,0);
  fflush(stdout);
}

void
event_init()
{
  estate.motionproc = NULL;
}

LDEFINE(event_mode, LVOID,
"(event-mode     MODESTRING)\n\
	Set the mouse event (motion) mode; MODESTRING should be one of\n\
	the strings that appears in the motion mode browser (including\n\
	the keyboard shortcut, e.g. \"[r] Rotate\").")
{
  static Event ev_enter = { EMODEENTER, 0, 0, 0, 0 };
  static Event ev_exit = { EMODEEXIT, 0, 0, 0, 0 };
  char *modename;

  LDECLARE(("event-mode", LBEGIN,
	    LSTRING, &modename,
	    LEND));

  if ( estate.motionproc != NULL ) {
    estate.motionproc(&ev_exit);
  }
  estate.motionproc = 
     uistate.modes[uistate.mode_current = ui_mode_index(modename) ];
  ui_event_mode( modename );
  if ( estate.motionproc != NULL ) {
    estate.motionproc(&ev_enter);
  }
  return Lt;
}



/*
 * Report time elapsed since the epoch (or since the program began if
 * since == NULL).   Possibly remember the current time in "nextepoch".
 * Time is measured in floating-point seconds.
 */
float
elapsed(float *since, float *nextepoch)
{
  static struct timeval t0 = { 0, 0 };
  struct timeval tnow;
  float now = 0;
  float sincetime = 0;

  gettimeofday(&tnow, NULL);
  if(t0.tv_sec == 0) {
    t0 = tnow;
    tnow.tv_usec++;
  }
  now = tnow.tv_sec - t0.tv_sec + 1e-6*(tnow.tv_usec - t0.tv_usec);
  if(since) {
    if((sincetime = *since) == 0)
	sincetime = *since = now;
  }
  if(nextepoch) *nextepoch = now;
  return now - sincetime;
}



  
LDEFINE(rawevent, LVOID,
"(rawevent       dev val x y t)\n\
	Enter the specified raw event into the event queue.  The\n\
	arguments directly specify the members of the event structure\n\
	used internally by geomview.  This is the lowest level event\n\
	handler and is not intended for general use.")
/*
  This used to be dispatch_event().
*/
{
  int id;
  int k;
  int err = 0;
  float v;
  Appearance *ap;
  DView *dv;
  DGeom *dg;
  char *s;

  Event event;
  LDECLARE(("rawevent", LBEGIN,
	    LINT, &event.dev,
	    LINT, &event.val,
	    LINT, &event.x,
	    LINT, &event.y,
	    LINT, &event.t,
	    LEND));

  /*
   * Call the current motion proc, if any.  This proc returns 1 if it
   * used the event, in which case we don't do any further processing.
   */
  if ( estate.motionproc != NULL ) {
    if ( estate.motionproc(&event) )
      return Lt;
  }

/* The rightmouse and doubleclick is now hardcoded but should be
   bindable through lang later, through a control mechanism similar to
   current motionproc stuff */

  if (event.dev == ERIGHTMOUSE) {
    if (event.val > 0) {
	static unsigned long int lastt = 0;
	Pick *pick = PickSet(NULL, PA_WANT, PW_EDGE|PW_VERT|PW_FACE, PA_END);
	int pickedid = view_pick( (DView *)drawer_get_object(FOCUSID),
				event.x, event.y, pick );

	if(button.shift) {
	  /* Could change FOCUSID here to ALLCAMS,
	   * to force setting everyone's focal length to 
	   * their distance from the pick.
	   */
	  if(pickedid != NOID)
	    make_center_from_pick("CENTER", pick, FOCUSID);
	  else
	    gv_ui_center(TARGETID);
	} else {
	  if(pickedid != NOID)
	    emit_pick(pickedid, pick);
	  if (event.t - lastt < DOUBLECLICKTIME) {
	    lastt = 0;
	    gv_ui_target( pickedid!=NOID ? pickedid : WORLDGEOM, IMMEDIATE );
	  }
	}
	PickDelete(pick);
	lastt = event.t;
    }
  }

  if(!isascii(event.dev))
	return Lt;

	/* Only keyboard events from here on down */

  ui_keyboard(event.dev);
  if(event.dev >= '0' && event.dev <= '9') {
    if(!hasnumber) {
	number = 0;
	hasnumber = 1;
    }
    number = 10*number + (event.dev - '0');
    if(expon) expon++;
  } else {

    id = GEOMID(uistate.targetgeom);

  rescan:

    switch(SEQ(prefix, event.dev)) {

    case '-':
    case '*':
	hasnumber = -1;
	expon = 0;
	number = 0;
	goto keepmode;

    case '.':
	expon = 1;
	goto keepmode;


    case ESC:
	gv_exit();
	/*NOTREACHED*/

    case 'g': keymode = KEYGEOM; goto gotmode; /* Select geom:  'g' prefix */
    case 'c': keymode = KEYCAM; goto gotmode;  /* Select camera: 'c' prefix */
  gotmode:
	if(hasnumber) gv_ui_target(retarget(NOID), IMMEDIATE);
	goto keepmode;

    case 'p':
	{
	  int id;
	  if (keymode == KEYNONE) {
	    id = gv_rawpick(FOCUSID, event.x, event.y);
	    if (id == NOID) id = WORLDGEOM;
	  } else {
	    id = retarget(NOID);
	  }
	  gv_ui_target(id, IMMEDIATE);
	}

    case '@':
	gv_ui_center(retarget(uistate.centerid));
	break;

    case 'N':
	id = retarget(GEOMID(uistate.targetgeom));
	if(!hasnumber) {
	    dg = (DGeom *)drawer_get_object(id);
	    if(dg) number = dg->normalization == NONE ? EACH : NONE;
	}
	drawer_int(id, DRAWER_NORMALIZATION, number);
	break;

    case '<':
	s = "Load"; number = 1; goto pickpanel;	/* Load file */
    case '>':
	s = "Save"; number = 1; goto pickpanel;	/* Save State */

		/* Halt current object */
    case 'h':
	drawer_stop(retarget(uistate.targetid)); break;

    case 'H':	/* Halt Everything */
	drawer_stop(NOID); break;

    case 'w':     /* Recenter current thing */
	drawer_center(retarget(uistate.targetid)); break;

    case 'W':     /* Recenter (and halt) Everything */
	drawer_center(NOID); break;

    case 'L':
	gv_look(retarget(uistate.targetid),FOCUSID); 
	break;
	
    /*
     * r/t/z/f/o apply to the currently selected object unless target specified.
     */
    case 'f': s = OBJFLY; goto mote;
    case 'o': s = OBJORBIT; goto mote;
    case 'r': s = OBJROTATE; goto mote;
    case 't': s = OBJTRANSLATE; goto mote;
    case 'z': s = OBJZOOM; goto mote;
    case 's': s = OBJSCALE; goto mote;

   mote:
	k = retarget(NOID);
	if (k) gv_ui_target( k, IMMEDIATE);
	gv_event_mode( s );
	break;

    case '?':
	print_help();
	break;

#ifdef sgi
    case 'T':   /* NTSC */
#endif
    case 'v':	/* view-related prefix */
    case 'a':	/* appearance-related prefix */
    case 'm':	/* metric (euclidean/hyperbolic/spherical) */
    case 'l':	/* light-related prefix */
    case 'd':   /* delete */
    case 'R':   /* renderman */
    case 'C':	/* color-pick */
    case 'P':	/* panel show */
    case 'u':	/* motion style */
	if(keymode != KEYNONE)
	    gv_ui_target(retarget(NOID), IMMEDIATE);
	prefix = event.dev;
	goto keepnumber;

    case SEQ('P','m'): 
    case SEQ('P','g'): s = "main"; goto pickpanel;
    case SEQ('P','a'): s = "Appearance"; goto pickpanel;
    case SEQ('P','o'): s = "Obscure"; goto pickpanel;
    case SEQ('P','l'): s = "Lighting"; goto pickpanel;
    case SEQ('P','C'): s = "Command"; goto pickpanel;
    case SEQ('P','c'): s = "Camera"; goto pickpanel;
    case SEQ('P','t'): s = "Tools"; goto pickpanel;
    case SEQ('P','f'): s = "Files"; goto pickpanel;
    case SEQ('P','s'): s = "Save"; goto pickpanel;
    case SEQ('P','M'): s = "Materials"; goto pickpanel;	
      pickpanel:
	if (!hasnumber) number = -1;
	ui_showpanel(ui_name2panel(s), number);
	break;
    case SEQ('P','-'): 
	comm_object("(read command < -)", &CommandOps, NULL, NULL, COMM_LATER);
	break;


    case SEQ('C','f'): k = DRAWER_DIFFUSE; goto pickcolor;
    case SEQ('C','e'): k = DRAWER_EDGECOLOR; goto pickcolor;
    case SEQ('C','n'): k = DRAWER_NORMALCOLOR; goto pickcolor;
    case SEQ('C','b'): k = DRAWER_BBOXCOLOR; goto pickcolor;
    case SEQ('C','v'):
    case SEQ('C','B'): k = DRAWER_BACKCOLOR; goto pickcolor;
     pickcolor:
	ui_pickcolor( k );
	break;

    case SEQ('u','i'): k = DRAWER_INERTIA; goto motstyle;
    case SEQ('u','c'): k = DRAWER_CONSTRAIN; goto motstyle;
    case SEQ('u','o'): k = DRAWER_OWNMOTION;
     motstyle:
	drawer_int( WORLDGEOM, k, hasnumber ? number : -1 );
	break;

    case SEQ('v','+'): 		/* Add camera */
      { CameraStruct cs;
	id = retarget(FOCUSID);
	dv = ISCAM(id) ? (DView *)drawer_get_object(id) : NULL;
	cs.h = NULL;
	cs.cam = dv && dv->cam ? CamCopy(dv->cam, NULL) : NULL; 
	gv_new_camera(NULL, &cs);
      }
	break;

    case SEQ('v','p'):		/* Projection: orthographic or perspective */
	id = retarget(FOCUSID);
	if(!hasnumber) {
	    dv = (DView *)drawer_get_object(id);
	    if(dv) CamGet(dv->cam, CAM_PERSPECTIVE, &number);
	    number = !number;
	}
	drawer_int( FOCUSID, DRAWER_PROJECTION, number );
	break;

    case SEQ('v','d'):			/* Draw other cameras */
	id = retarget(FOCUSID);
	if(!hasnumber) {
	    dv = (DView *)drawer_get_object(id);
	    number = dv ? !dv->cameradraw : 1;
	}
	drawer_int( id, DRAWER_CAMERADRAW, number );
	break;

    case SEQ('v','x'): /* Toggle/enable/disable cursor */
	ui_curson( hasnumber ? number : -1 ); 
	break;

    case SEQ('v','b'):
	gv_cull_backface( uistate.backface = toggle(uistate.backface) );
	break;
	
    case SEQ('v','s'):
	id = retarget(FOCUSID);
	drawer_int( id, DRAWER_DOUBLEBUFFER, hasnumber ? number : -1 );
	break;

	/* For testing software shading */
    case SEQ('v','~'):
	id = retarget(FOCUSID);
	gv_soft_shader(id,
		hasnumber ? (number?ON_KEYWORD:OFF_KEYWORD) : TOGGLE_KEYWORD);
	break;


	/* Viewing options */
    case SEQ('a','c'): 
    case SEQ('v','c'): k = DRAWER_LINE_ZNUDGE; v = 10.; goto setcam;
    case SEQ('v','v'): k = DRAWER_FOV;  v = 45.; goto setcam;
    case SEQ('v','n'): k = DRAWER_NEAR; v = .1;	goto setcam;
    case SEQ('v','f'): k = DRAWER_FAR;  v = 100.; goto setcam;
    case SEQ('v','l'): k = DRAWER_FOCALLENGTH; v = 3.; goto setcam;
     setcam:
	drawer_float( retarget(FOCUSID), k, getreal(v) );
	break;

	/* Might add others here, e.g. a viewfinder mode. */

	/* Metrics / Models */
    case SEQ('m','e'):
      gv_space(EUCLIDEAN_KEYWORD);
      break;
    case SEQ('m','h'):
      gv_space(HYPERBOLIC_KEYWORD);
      break;
    case SEQ('m','s'):
      gv_space(SPHERICAL_KEYWORD);
      break;
    case SEQ('m','v'):
      gv_hmodel(CAMID(uistate.targetcam), VIRTUAL_KEYWORD);
      break;
    case SEQ('m','p'):
      gv_hmodel(CAMID(uistate.targetcam), PROJECTIVE_KEYWORD);
      break;
    case SEQ('m','c'):
      gv_hmodel(CAMID(uistate.targetcam), CONFORMALBALL_KEYWORD);
      break;

	/* Appearance settings */
    case SEQ('a','f'): tog_ap_flag( id, APF_FACEDRAW ); break;
    case SEQ('a','e'): tog_ap_flag( id, APF_EDGEDRAW ); break;
    case SEQ('a','n'): tog_ap_flag( id, APF_NORMALDRAW ); break;
    case SEQ('a','v'): tog_ap_flag( id, APF_EVERT ); break;
    case SEQ('a','T'): tog_ap_flag( id, APF_TRANSP ); break;
    case SEQ('a','V'): tog_ap_flag( id, APF_VECTDRAW ); break;
    case SEQ('a','x'): drawer_set_ap( id, NULL, NULL ); break;
    case SEQ('a','o'): gv_ap_override( hasnumber ? number : !uistate.apoverride );
			break;


    case SEQ('a','b'): /* Bounding box drawing */
	if(!hasnumber) {
	    DGeom *dg = (DGeom *)drawer_get_object( id );
	    if(dg) number = !dg->bboxdraw;
	}
	drawer_int(id, DRAWER_BBOXDRAW, number);
	break;

    case SEQ('a','s'):	/* Shading */
	if(!hasnumber) {
	    ap = drawer_get_ap(id);
	    ApGet(ap, AP_SHADING, &number);
	    ApDelete(ap);
	    number++;
	}
	drawer_int( id, DRAWER_SHADING, number&3);
	break;

    case SEQ('a','w'):	/* line width */
        if(!hasnumber) {
	    ap = drawer_get_ap(id);
	    ApGet(ap, AP_LINEWIDTH, &number);
	    ApDelete(ap);
	    number = (number > 1) ? 1 : 2;
	}
	drawer_int( id, DRAWER_LINEWIDTH, number);
	break;

	/* Scale normals */
    case SEQ('a','h'): drawer_float(id, DRAWER_NORMSCALE, getreal(1.0)); break;

	/* Patch dicing */
    case SEQ('a','d'): drawer_int( id, DRAWER_BEZDICE, number ); break;

	/* hyperbolic sphere at infinity */
    case SEQ('a', 'i'): drawer_int( TARGETCAMID, DRAWER_HSPHERE, -1 ); break;
	
	/* Delete */
    case SEQ('d','d'): gv_delete(uistate.targetid); break;

	/* NTSC */
#ifdef sgi
    case SEQ('T','V'): if (!hasnumber) number = -1; ntsc(number); break;
#endif

	/* Timing -- ctrl-T
	 * ^T : print accumulated timing status now
	 * <nnn>^T : print timing status now and every <nnn> main-loop cycles
	 * -^T : quit timing
	 */
    case 'T'&0x1f:
	timing( hasnumber<0 ? 0 : hasnumber ? number : 9999999 );
	break;

	/* Edit Lights */
    case SEQ('l','e'):
      if (!(uistate.lights_shown)) light_edit_mode(1);
      else gv_event_mode( LIGHTEDIT );
	break;
	
	/* Toggle Show Lights */
    case SEQ('l','s'): light_edit_mode(2); break;

    /*
     * All R* commands moved to rman.c - slevy.
     */
    default:
	err = EOF;
	if(prefix == 'R') {
	    rman_do(event.dev,hasnumber,number);
	} else if(prefix != 0) {		/* No such command? */
	    prefix = 0;
	    goto rescan;		/* Try same char without prefix */
	}
	keymode = KEYNONE;
    }
    hasnumber = expon = 0;
    prefix = 0;
    ui_keyboard(err);			/* 0 = OK, EOF = -1 = error */
  keepnumber:
    keymode = KEYNONE;
  keepmode:
    ;
  }

  return Lt;
}

/* 
 * Interpret a g[N] or c[N] prefix; return the id.
 */
static int
retarget(int defindex)
{
  int t;
  static int allid[2] = { ALLGEOMS, ALLCAMS };
  static char ch[2] = { 'g', 'c' };
  char code[12];

  if(keymode == KEYNONE) {
	if(expon && !hasnumber) {	/* a "." prefix, sans number */
	    expon = 0;
	    return TARGETID;
	}
	return defindex;		/* No prefix, or just numeric */
  }

  sprintf(code, "%c%d", ch[keymode], number);
  if(hasnumber > 0) t = drawer_idbyname(code);
  else if(hasnumber < 0) t = allid[keymode];
  else t = (keymode == KEYGEOM) ? WORLDGEOM
				: FOCUSID;
  hasnumber = expon = 0;
  keymode = KEYNONE;
  return t;
}

static float
getreal(float defval)
{
    float v = hasnumber * number;

    if(!hasnumber) return defval;
    while(--expon > 0)
	v *= 0.1;
    return v;
}

static int
toggle(int val)
{
  return hasnumber ? (hasnumber = expon = 0, number) : !val;
}

static void
tog_ap_flag( int id, int flagbit )
{
    ApStruct as;
    int val;

    if(hasnumber) {
	val = number;
    } else {
	as.ap = drawer_get_ap(id);
	val = as.ap ? !(as.ap->flag & flagbit) : 1;
    }
    as.ap = ApCreate(val ? AP_DO : AP_DONT, flagbit,
		AP_OVERRIDE, uistate.apoverride & flagbit, AP_END);
    gv_merge_ap(id, &as);
    ApDelete(as.ap);
}



/* NB - I've put in a total hack to avoid calling gvpick more than 
 * once - I think it's pretty stable (so when it stays in for years
 * and years it might(?) keep working!) -cf 10/29/92 */
static int
view_pick( DView *dv, int x, int y, Pick *pick )
{
  Transform V, T, Tnet, Tt, Tmodel, Tnorm, oldTw, Tworld;
  register int i;
  int chosen = NOID;
  float xpick, ypick;
  DGeom *dg;
  Appearance *ap;

  Point3 got,v,e[2],wgot,wv,we[2], owgot, owv, owe[2];
  
  if(dv == NULL)
    return NOID;

  if(dv->stereo == NO_KEYWORD) {
    mousemap(x, y, &xpick, &ypick, &drawerstate.winpos);
  } else {
    /* Map screen -> view position in a stereo window.
     */
    for(i = 0; i < 2; i++) {
	WnPosition wp;
	wp.xmin = drawerstate.winpos.xmin + dv->vp[i].xmin;
	wp.xmax = wp.xmin + dv->vp[i].xmax - dv->vp[i].xmin;
	wp.ymin = drawerstate.winpos.ymin + dv->vp[i].ymin;
	wp.ymax = wp.ymin + dv->vp[i].ymax - dv->vp[i].ymin;
	mousemap(x, y, &xpick, &ypick, &wp);
	if(fabs(xpick) <= 1 && fabs(ypick) <= 1)
	    break;
    }
  }
    
  CamView( dv->cam, V );	/* V = camera-to-screen matrix */

  if(dv->Item != drawerstate.universe) {
	/* Picking in a window with a dedicated Scene */
	/* We yield results in the Scene's coordinate system */
    GeomPosition( dv->Item, T );
    TmConcat(T,V, T);		/* T = Scene to screen projection */
    if(GeomMousePick( dv->Item, pick, (Appearance *)NULL, T, xpick, ypick )) {
	chosen = dv->id;
    }
    return chosen;
  }

	/* Picking in the real world */
  GeomPosition( drawerstate.world, Tworld );
  TmConcat( Tworld,V, T );
  /*
   * We now assume the complete screen -> DGeom transform is in T.
   * This is true only if we have just a single level of DGeom's in the world.
   */
  LOOPSOMEGEOMS(i,dg,ORDINARY) {
    if (dg->pickable) {
      Geom *g = NULL;
      if (dg->Lgeom) {
	GeomPosition( dg->Item, Tmodel );
	GeomPosition( dg->Inorm, Tnorm );
	TmConcat( Tnorm,Tmodel, Tt );
	TmConcat( Tt,T, Tnet );	/* Now Tnet = complete geom-to-screen proj'n */
	GeomGet( dg->Lgeom, CR_GEOM, &g );
	ap = drawer_get_ap(i);
	if(GeomMousePick( g, pick, ap,
			 Tnet, xpick, ypick )) {
	  chosen = GEOMID(i);
	  /* We remember oldTw to print out info below for debugging only */
	  TmCopy(pick->Tw, oldTw);
	  /* This is necessary!  Arranges for things to be in world coords
	     not Dgeom coords. Tt is the world-to-dgeom transform 
	     (or vice versa?) */
	  TmConcat(pick->Tw, Tt, pick->Tw);
	  drawer_get_transform(WORLDGEOM, pick->Tself, GEOMID(i));
	  TmConcat(pick->Tw, pick->Tself, pick->Tself);
	}
	ApDelete(ap);
      }
    }
  }

  if (chosen == NOID) {
/*    printf("Picked nothing.\n"); */
  }
  else {
/*    printf("Picked dgeom #%d\n", INDEXOF(chosen)); */
    
/* pick->got is in mouse coords.
   wgot is world coords.
   old world is really dgeom coords. (maybe...)
   got is raw object coords. (the kind of numbers in geom data file!)
    
*/
      if (pick && getenv("VERBOSE_PICK")) {
	
	Pt3Transform(pick->Tmirp, &(pick->got), &got);
	if (pick->found&PW_VERT)
	  Pt3Transform(pick->Tmirp, &(pick->v), &v);
	if (pick->found&PW_EDGE) {
	  Pt3Transform(pick->Tmirp, &(pick->e[0]), &(e[0]));
	  Pt3Transform(pick->Tmirp, &(pick->e[1]), &(e[1]));
	}
	
	Pt3Transform(pick->Tw, &(pick->got), &wgot);
	if (pick->found&PW_VERT)
	  Pt3Transform(pick->Tw, &(pick->v), &wv);
	if (pick->found&PW_EDGE) {
	  Pt3Transform(pick->Tw, &(pick->e[0]), &(we[0]));
	  Pt3Transform(pick->Tw, &(pick->e[1]), &(we[1]));
	}
	
	Pt3Transform(oldTw, &(pick->got), &owgot);
	if (pick->found&PW_VERT)
	  Pt3Transform(oldTw, &(pick->v), &owv);
	if (pick->found&PW_EDGE) {
	  Pt3Transform(oldTw, &(pick->e[0]), &(owe[0]));
	  Pt3Transform(oldTw, &(pick->e[1]), &(owe[1]));
	}
	
	printf("pick->\n");
	printf("  got = (%f %f %f)\n", pick->got.x, pick->got.y, pick->got.z);
	if (pick->found&PW_VERT)
	  printf("    v = (%f %f %f)\n", pick->v.x, pick->v.y, pick->v.z);
	if (pick->found&PW_EDGE) {
	  printf(" e[0] = (%f %f %f)\n", pick->e[0].x, pick->e[0].y, pick->e[0].z);
	  printf(" e[1] = (%f %f %f)\n", pick->e[1].x, pick->e[1].y, pick->e[1].z);
	}
	
	
	printf("Transformed pick [raw]->\n");
	printf("  got = (%f %f %f)\n", got.x, got.y, got.z);
	if (pick->found&PW_VERT)
	  printf("    v = (%f %f %f)\n", v.x, v.y, v.z);
	if (pick->found&PW_EDGE) {
	  printf(" e[0] = (%f %f %f)\n", e[0].x, e[0].y, e[0].z);
	  printf(" e[1] = (%f %f %f)\n", e[1].x, e[1].y, e[1].z);
	}
	
	printf("Transformed pick [old world]->\n");
	printf("  got = (%f %f %f)\n", owgot.x, owgot.y, owgot.z);
	if (pick->found&PW_VERT)
	  printf("    v = (%f %f %f)\n", owv.x, owv.y, owv.z);
	if (pick->found&PW_EDGE) {
	  printf(" e[0] = (%f %f %f)\n", owe[0].x, owe[0].y, owe[0].z);
	  printf(" e[1] = (%f %f %f)\n", owe[1].x, owe[1].y, owe[1].z);

	printf("Transformed pick [world]->\n");
	printf("  got = (%f %f %f)\n", wgot.x, wgot.y, wgot.z);
	if (pick->found&PW_VERT)
	  printf("    v = (%f %f %f)\n", wv.x, wv.y, wv.z);
	if (pick->found&PW_EDGE) {
	  printf(" e[0] = (%f %f %f)\n", we[0].x, we[0].y, we[0].z);
	  printf(" e[1] = (%f %f %f)\n", we[1].x, we[1].y, we[1].z);
	}


	}
      }
  }
  return chosen;
}

LDEFINE(rawpick, LINT,
	"(rawpick CAMID X Y)\n\
	Process a pick event in camera CAMID at location (X,Y) given in\n\
	integer pixel coordinates.  This is a low-level procedure not\n\
	intended for external use.")
{
  int pickedid, id, x, y;
  Pick *pick;

  LDECLARE(("rawpick", LBEGIN,
	    LID, &id,
	    LINT, &x,
	    LINT, &y,
	    LEND));
  if (TYPEOF(id) != T_CAM) {
    fprintf(stderr, "rawpick: first arg must be a camera id\n");
    return Lnil;
  }

  pick = PickSet(NULL, PA_WANT, PW_EDGE|PW_VERT|PW_FACE, PA_END);
  pickedid= view_pick( (DView *)drawer_get_object(id), x, y, pick );

  emit_pick(pickedid, pick);
  PickDelete(pick);
  return LNew(LINT, &pickedid);
}

void
emit_pick(int pickedid, Pick *pick)
{
  /* Variables for total hack */
  vvec done;
  char donebits[512];

  LInterest *interest = LInterestList("pick");

  VVINIT(done, char, 128);
  if(interest) {
    vvuse(&done, donebits, COUNT(donebits));
    vvzero(&done);
  }

#define DONEID(id) *VVINDEX(done, char, id-CAMID(-20))

  for ( ; interest != NULL; interest = interest->next) {
    Transform T;
    float got[4], v[4], e[8];
    HPoint3 pgot, *f;
    int gn, vn, vi, en, ei[2], ein, coordsysid, fn, fi;

    /* extract the coord system to use from the interest filter;
       if none given, use world */
    if (   interest->filter
	&& interest->filter->car
	&& (LFILTERVAL(interest->filter->car)->flag == VAL)) {
      if (!LFROMOBJ(LID)(LFILTERVAL(interest->filter->car)->value,
			 &coordsysid)) {
	OOGLError(0,"rawpick: bad coord sys filter type");
	continue;
      }
    } else {
      coordsysid = WORLDGEOM;
    }

    /*  T = transform converting to the coord system of coordsysid */
    /* This section does the setup for the total hack */

    /* Total hack gigantic if statement */
    if (!DONEID(coordsysid)) {
      DONEID(coordsysid) = 1;
      switch(coordsysid) {
      case WORLDGEOM:
	TmCopy(pick->Tw, T); break;
      case PRIMITIVE:
	TmCopy(pick->Tmirp, T); break;
      case SELF:
	TmCopy(pick->Tself, T); break;
      default:
	drawer_get_transform(WORLDGEOM, T, coordsysid);
	TmConcat(pick->Tw, T, T);
	break;
      }

      if (pickedid != NOID) {
	pgot.x = pick->got.x;
	pgot.y = pick->got.y;
	pgot.z = pick->got.z;
	pgot.w = 1;
	HPt3Transform(T, &pgot, got);
	gn = 4;
      } else
	gn = 0;
      
      if (pick->found&PW_VERT) {
	HPt3Transform(T, &(pick->v), v);
	vn = 4;
	vi = pick->vi;
      } else {
	vn = 0;
	vi = -1;
      }
      
      if (pick->found&PW_EDGE) {
	HPt3TransformN(T, pick->e, e, 2);
	en = 8;
	ei[0] = pick->ei[0];
	ei[1] = pick->ei[1];
	ein = 2;
      } else {
	en = 0;
	ein = 0;
      }
      if (pick->found & PW_FACE) {
	f = OOGLNewNE(HPoint3, pick->fn, "rawpick");
	HPt3TransformN(T, pick->f, f, pick->fn);
	fi = pick->fi;
	fn = pick->fn * 4;
      }
      else {
	f = NULL;
	fn = 0;
	fi = -1;
      }
      
      /* Cause of total hack.
       * This CANNOT be called once for every interested party - otherwise
       * every interested party will hear about it numerous times. */
      gv_pick(coordsysid, pickedid,
	      got, gn,
	      v, vn,
	      e, en,
	      (float *)f, fn, 	/* f, fn, */
	      VVEC(pick->gpath, int), VVCOUNT(pick->gpath),
	      vi,
	      ei, ein,
	      fi
	      );

      if (f != NULL) OOGLFree(f);

    } /* End of total hack if statement */
  }
  vvfree(&done);
}

LDEFINE(pick, LVOID,
	"(pick COORDSYS GEOMID G V E F P VI EI FI)\n\
	The pick command is executed internally in response to pick\n\
	events (right mouse double click).\n\
\n\
	COORDSYS = coordinate system in which coordinates of the following\n\
	    arguments are specified.   This can be:\n\
		world: world coord sys\n\
		self:  coord sys of the picked geom (GEOMID)\n\
		primitive: coord sys of the actual primitive within\n\
		    the picked geom where the pick occurred.\n\
	GEOMID = id of picked geom\n\
	G = picked point (actual intersection of pick ray with object)\n\
	V = picked vertex, if any\n\
	E = picked edge, if any\n\
	F = picked face\n\
	P = path to picked primitive [0 or more]\n\
	VI = index of picked vertex in primitive\n\
	EI = list of indices of endpoints of picked edge, if any\n\
	FI = index of picked face\n\
\n\
	External modules can find out about pick events by registering\n\
	interest in calls to \"pick\" via the \"interest\" command.")
{
  float got[4], v[4], e[8], f[40];
  int vi, ei[8], fi, p[40];
  int gn=4, vn=4, en=8, fn=40, pn = 40;
  int ein=8;
  int id, coordsys;


  /* NOTE: If you change the lisp syntax of this function (which you
     shouldn't do), you must also update the DEFPICKFUNC macro in the
     file "pickfunc.h", which external modules use. */
  LDECLARE(("pick", LBEGIN,
	    LID, &coordsys,
	    LID, &id,
	    LHOLD, LARRAY, LFLOAT, got, &gn,
	    LHOLD, LARRAY, LFLOAT, v, &vn,
	    LHOLD, LARRAY, LFLOAT, e, &en,
	    LHOLD, LARRAY, LFLOAT, f, &fn,
	    LHOLD, LARRAY, LINT, p, &pn,
	    LINT, &vi,
	    LHOLD, LARRAY, LINT, ei, &ein,
	    LINT, &fi,
	    LEND));
  return Lt;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.