Skip to content
Snippets Groups Projects
Gview.cc 27.8 KiB
Newer Older
/*
 * Geomview-style 3-D view widget for FLTK.
 * Stuart Levy, slevy@ncsa.uiuc.edu
 * National Center for Supercomputing Applications,
 * University of Illinois 2001.
 * This file is part of partiview, released under the
 * Illinois Open Source License; see the file LICENSE.partiview for details.
slevy's avatar
slevy committed

#ifdef _WIN32
# include "winjunk.h"
#endif

teuben's avatar
teuben committed
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "geometry.h"
#include "textures.h"	/* for set_dsp_context() */
#include <memory.h>
teuben's avatar
teuben committed

#include "Gview.H"

slevy's avatar
slevy committed
#ifndef wallclock_time
 extern "C" { extern double wallclock_time(void); }	// from sclock.c
#endif
teuben's avatar
teuben committed

static void translation(Matrix *T, float x, float y, float z);
static void rotation(Matrix *T, float x, float y, float z);

Fl_Gview::Fl_Gview(int x, int y, int w, int h, const char *label)
		: Fl_Gl_Window(x,y,w,h,label) {
  init();
  end();
}

void Fl_Gview::glmode( int bits )
{
  if(this->dspcontext() >= 0 && this->shown() && bits != this->mode()) {
    /* It'll probably require a new window, and all our
     * opengl context will get lost.
     * Let textures.c know about this so it can reinit.
     */
    this->dspcontext( Fl_Gview::next_dspcontext() );
  }
  this->mode( bits );
}

int Fl_Gview::next_dspcontext_ = 0;

int Fl_Gview::next_dspcontext()
{
   return next_dspcontext_++;
}

void Fl_Gview::dspcontext( int dspctx )
{
  if(next_dspcontext_ <= dspctx)
    next_dspcontext_ = dspctx + 1;
  this->dspcontext_ = dspctx;
}

teuben's avatar
teuben committed
void Fl_Gview::lookvec(int axis, const Point *vec) {
  Point v = *vec;
  if(axis < 0 || axis > 3) return;
  if(axis == 2) vscale(&v, -1,&v);	/* cam looks toward -Z */
  /* XXX fill in orthogonalization, invert, load into Tw2c */
  /* Don't need this right now */
  Fl::warning("Fl_Gview::lookvec() not implemented yet");
  redraw();
}

const Matrix *Fl_Gview::Tc2w() const {
  return &Tc2w_;
}

const Matrix *Fl_Gview::Tw2c() const {
  return &Tw2c_;
}

void Fl_Gview::Tc2w( const Matrix *newTc2w ) {
  Tc2w_ = *newTc2w;
  eucinv(&Tw2c_, &Tc2w_);
  notify();
  redraw();
}

void Fl_Gview::Tw2c( const Matrix *newTw2c ) {
  Tw2c_ = *newTw2c;
  eucinv(&Tc2w_, &Tw2c_);
  notify();
  redraw();
}

void Fl_Gview::reset( int id ) {
  if(id == GV_ID_CAMERA) {
    translation( &Tc2w_, 0, 0, focallen_ );
    Tc2w( &Tc2w_ );
  } else {
    const Matrix *o2w = To2w( id );
    if(o2w && o2w != &Tidentity) {
	*(Matrix *)o2w = Tidentity;	/* Reset! */
    }
  }
  notify();
  redraw();
}

void Fl_Gview::focallen(float flen) {
  if(flen == 0) {
    Fl::warning("Can't set Fl_Gview::focallen() to zero");
  } else {
    if(persp_)
	halfyfov_ *= flen / focallen_;
    focallen_ = flen;
  }
  notify();
  redraw();
}

float Fl_Gview::angyfov() const {
  return 2*atan(halfyfov_ / focallen_)*180/M_PI;
}

void Fl_Gview::angyfov(float deg) {
  if(deg == 0) Fl::warning("Can't set Fl_Gview::angyfov() to zero");
  else if(deg <= -180 || deg >= 180)
	Fl::warning("Can't set Fl_Gview::angyfov() to >= 180");
  else halfyfov_ = focallen_ * tan((deg/2)*M_PI/180);
  notify();
  redraw();
}

void Fl_Gview::halfyfov( float hyfov ) {
  if(hyfov == 0) Fl::warning("Can't set Fl_Gview::halfyfov() to zero");
  else halfyfov_ = hyfov;
  redraw();
}

void Fl_Gview::perspective( int bepersp ) {
  if((bepersp!=0) == persp_)
    return;
  persp_ = (bepersp != 0);
  redraw();
  notify();
}

void Fl_Gview::farclip( float cfar ) {
  if(cfar == far_) return;
  if(cfar != 0 || !persp_) {
    far_ = cfar;
    notify();
    redraw();
  }
}

void Fl_Gview::nearclip( float cnear ) {
  if(cnear == near_) return;
  if(cnear != 0 || !persp_) {
    near_ = cnear;
    notify();
    redraw();
  }
}

slevy's avatar
slevy committed
void Fl_Gview::stereooffset( int newoff ) {
  if(newoff != stereooff_) {
    stereooff_ = newoff;
    notify();
    redraw();
  }
}

void Fl_Gview::pixelaspect( float pixasp ) {
  if(pixasp != 0 && pixasp != pixelaspect_) {
    pixelaspect_ = pixasp;
    notify();
    redraw();
  }
}


teuben's avatar
teuben committed
void Fl_Gview::center( const Point *cenw ) {
  if(cenw) pcenw_ = *cenw;
  else pcenw_.x[0] = pcenw_.x[1] = pcenw_.x[2] = 0;
  notify();
}

int Fl_Gview::next_id() const {
  int id;
  for(id = 1; withid(id) >= 0; id++)
    ;
  return id;
}

int Fl_Gview::add_drawer( void (*func)( Fl_Gview *, void *obj, void *arg ),
	void *obj, void *arg, char *name, int id )
{
  if(id < 0) id = next_id();
  int dno = withid( id );
  if(dno < 0) {
    if(ndrawers_ >= maxdrawers_) {
	maxdrawers_ = ndrawers_*2 + 15;
	int room = maxdrawers_ * sizeof(struct drawer);
	drawers_ = (struct drawer *)
		    (drawers_==NULL ? malloc(room) : realloc(drawers_, room));
    }
    dno = ndrawers_++;
  }
  struct drawer *dp = &drawers_[dno];
  dp->func = func;
  dp->obj = obj;
  dp->arg = arg;
  dp->To2w = Tidentity;
  dp->name = name;
  dp->id = id;
  dp->objclip = 0;
  dp->objnear = 0;
  dp->objfar = 0;
teuben's avatar
teuben committed
  notify();
  redraw();
  return id;
}

int Fl_Gview::withid( int id ) const {	// Which drawer[] slot is id in?
teuben's avatar
teuben committed
  for(int dno = 0; dno < ndrawers_; dno++)
    if(drawers_[dno].id == id)
	return dno;
  return -1;
}

const Matrix *Fl_Gview::To2w( int id ) const {
  if(id == GV_ID_CAMERA)
    return Tc2w();
  int dno = withid( id );
  return (dno < 0) ? &Tidentity : &drawers_[dno].To2w;
}

int Fl_Gview::To2w( int id, const Matrix *newT ) {
  if(id == GV_ID_CAMERA) {
    Tc2w( newT );
    return -1;
  }
  int drawerno = withid( id );
  if(drawerno < 0) return 0;
  drawers_[drawerno].To2w = newT ? *newT : Tidentity;
  notify();
  redraw();
  return 1;
}

void Fl_Gview::objparent( int id, int parent ) {
  int drawerno = withid( id );
  if(drawerno >= 0)
    drawers_[drawerno].parent = parent;
}

int Fl_Gview::objparent( int id ) const {
  int drawerno = withid( id );
  return (drawerno >= 0) ? drawers_[drawerno].parent : 0;
}

void Fl_Gview::subc_lrbt( float subclrbt[4] ) {
  for(int k = 0; k < 4; k++)
    subclrbt_[k] = subclrbt[k];
}

teuben's avatar
teuben committed
#define VIEW_CLEAR  0x1
#define	VIEW_RED    0x2
#define	VIEW_CYAN   0x4

void Fl_Gview::glprojection( float nearclip, float farclip, const Matrix *postproj )
{
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    if(inpick()) {
	GLint vp[4] = {0, 0, w(), h()};
	if(stereo_ == GV_CROSSEYED) {
	    /* Jigger viewport -- choose whichever half this pick came from */
	    int myw = (w() - stereooff_)/2;
	    vp[2] = myw;
	    if(pickx_ > myw) vp[0] = w() - myw;
	}
	gluPickMatrix( pickx_, picky_, pickwidth_, pickheight_, vp );
    }
    if(use_subc_) {
	glFrustum( nearclip * subclrbt_[0], nearclip * subclrbt_[1],
		   nearclip * subclrbt_[2], nearclip * subclrbt_[3],
		   nearclip, farclip );

    } else if(persp_) {
	float nthf = nearclip * halfyfov_ / focallen_;
	glFrustum( -nthf * aspect_, nthf * aspect_,
		   -nthf, nthf,
		   nearclip, farclip );
    } else {
	glOrtho( -aspect_*halfyfov_, aspect_*halfyfov_,
		 -halfyfov_, halfyfov_,
		  nearclip, farclip );
    }
    if(postproj)
	glMultMatrixf( postproj->m );
    glMatrixMode( GL_MODELVIEW );
}

teuben's avatar
teuben committed
void Fl_Gview::draw_scene( int how, const Matrix *postproj ) {
  /* draw scene */
slevy's avatar
slevy committed
  float then = wallclock_time();
teuben's avatar
teuben committed

  if(this->dspcontext() >= 0)
    set_dsp_context( this->dspcontext() );	/* alert texture layer */

  if(focalpointing_) {
      /* Compute distance from camera point to focal point.
       * Or, should we do this as a focal-plane distance instead?
       * If we just compute a point distance, it'll make sense even
       * if the point is behind the camera plane.
       * Note that the focalpoint is specified in *world* coordinates.
       */
    Point fpcam;
    vtfmpoint( &fpcam, &focalpoint_, Tw2c() );
    float dist = vlength( &fpcam );
    focallen( (dist > minfocallen_) ? dist : minfocallen_ );
  }

teuben's avatar
teuben committed
  glClearDepth( 1.0 );
  if(how & VIEW_CLEAR) {
    glColorMask( 1, 1, 1, 1 );
    glClearColor( bgcolor_.x[0], bgcolor_.x[1], bgcolor_.x[2], 0 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  } else {
    glClear( GL_DEPTH_BUFFER_BIT );
  }

  if(how & VIEW_RED)
    glColorMask( 1, 0, 0, 1 );
  else if(how & VIEW_CYAN)
    glColorMask( 0, 1, 1, 1 );

  glEnable( GL_DEPTH_TEST );
  glDisable( GL_LIGHTING );
  glDisable( GL_TEXTURE_2D );
  glDisable( GL_COLOR_MATERIAL );

  if(how || postproj)
    glprojection( near_, far_, postproj );
teuben's avatar
teuben committed


    if(predraw)
      (*predraw)( this, again );

    if(how || postproj) {
      glMatrixMode( GL_MODELVIEW );
      if(use_subc_) {
	glLoadMatrixf( Tc2subc()->m );
	glMultMatrixf( Tw2c()->m );
      } else {
	glLoadMatrixf( Tw2c()->m );
      }
    }
    for(int i = 0; i < ndrawers_; i++) {
      struct drawer *dp = &drawers_[i];
      if(dp->func != NULL) {
	int wantclip = dp->objclip;
	if(wantclip)
	  glprojection( dp->objnear, dp->objfar, postproj );
	else if(curclip)
	  glprojection( near_, far_, postproj );
	curclip = wantclip;

	glPushMatrix();
	if(dp->parent == GV_ID_CAMERA) {
	  if(use_subc_)
	    glLoadMatrixf( Tc2subc()->m );
	  else
	    glLoadIdentity();
	}
	glMultMatrixf( dp->To2w.m );
	if(inpick_) {
	  glLoadName( dp->id );
	  glPushName(0);
teuben's avatar
teuben committed
	}
	(*dp->func)(this, dp->obj, dp->arg);
	if(inpick_)
	  glPopName();
	glPopMatrix();
      }
    }
    if(postdraw)
      again = (*postdraw)( this, again );

    if(again) {
      //marx added to 0.7.06  to address 'duplicate' image when using multiple channels
      glClearDepth( 1.0 );
      if(how & VIEW_CLEAR) {
	glColorMask( 1, 1, 1, 1 );
	glClearColor( bgcolor_.x[0], bgcolor_.x[1], bgcolor_.x[2], 0 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      } else {
	glClear( GL_DEPTH_BUFFER_BIT );
      }
      //end marx added to 0.7.06 to address 'duplicate' image when using multiple channels
    }

slevy's avatar
slevy committed

  rateaccum( wallclock_time() - then );
  framecount++;
teuben's avatar
teuben committed
}

static void stereoeye( Matrix *dst, float stereosep, float focallen ) {
  *dst = Tidentity;
  dst->m[4*2+0] = stereosep;
  dst->m[4*3+0] = stereosep * focallen;
}

void Fl_Gview::draw() {

  if(!valid() || damage() || inpick() || (stereo_ != GV_MONO)) {
    /* Assume reshaped */
    valid(1);
    glViewport( 0, 0, w(), h() );

#if defined(FL_MULTISAMPLE) && defined(GL_MULTISAMPLE_SGIS)
    if(this->mode() & FL_MULTISAMPLE)
	glEnable(GL_MULTISAMPLE_SGIS);
#endif

slevy's avatar
slevy committed
    aspect_ = h() > 0 ? pixelaspect_ * (float)w() / (float)h() : 1.0;
teuben's avatar
teuben committed

    Matrix postproj;

    switch(stereo_) {
    case GV_MONO:
    default:
	draw_scene( VIEW_CLEAR, NULL );
	break;

    case GV_REDCYAN:
	stereoeye( &postproj, stereosep_, focallen_ );
	draw_scene( VIEW_CLEAR|VIEW_RED, &postproj );
	stereoeye( &postproj, -stereosep_, focallen_ );
	draw_scene( VIEW_CYAN, &postproj );
	glColorMask( 1, 1, 1, 1 );
	break;

    case GV_QUADBUFFERED:
	stereoeye( &postproj, -stereosep_, focallen_ );
	glDrawBuffer( GL_BACK_RIGHT );
	draw_scene( VIEW_CLEAR, &postproj );

	stereoeye( &postproj, stereosep_, focallen_ );
	glDrawBuffer( GL_BACK_LEFT );
	draw_scene( VIEW_CLEAR, &postproj );
teuben's avatar
teuben committed

    case GV_LEFTEYE:
	stereoeye( &postproj, stereosep_, focallen_ );
	draw_scene( VIEW_CLEAR, &postproj );
	break;

    case GV_RIGHTEYE:
	stereoeye( &postproj, -stereosep_, focallen_ );
	draw_scene( VIEW_CLEAR, &postproj );
teuben's avatar
teuben committed
	break;
slevy's avatar
slevy committed

    case GV_CROSSEYED:
	int myw, myh;
	myw = (w() - stereooff_)/2;	/* or w()/2 - halfgap */
	myh = h();
	aspect_ = myh > 0 ? pixelaspect_ * myw / (float)myh : 1.0;
	
	stereoeye( &postproj, -stereosep_, focallen_ );
	glViewport( 0, 0, myw, myh );	/* right-eye view drawn on left side */
slevy's avatar
slevy committed
	draw_scene( VIEW_CLEAR, &postproj );

	stereoeye( &postproj, stereosep_, focallen_ );
slevy's avatar
slevy committed
	glViewport( w()-myw, 0, myw, myh );
	draw_scene( 0, &postproj );
	break;
teuben's avatar
teuben committed
    }
  } else {
    draw_scene( VIEW_CLEAR, NULL );
  }

  /* draw (I hope) any children lying on top of us */
  if(children() > 0) Fl_Gl_Window::draw();
}

int Fl_Gview::snapshot( int x, int y, int w, int h,  void *packedrgb )
{
teuben's avatar
teuben committed
  make_current();
  glPixelStorei( GL_PACK_ALIGNMENT, 1 );
  glReadBuffer( GL_FRONT );
  glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, packedrgb);
  return 1; // Might return whether this window was properly uncovered?
}

slevy's avatar
slevy committed
void Fl_Gview::takeMausSample( int ev, float mintime ) {
    float now = wallclock_time();
    if(ev == FL_PUSH)
	nms_ = 0;
    if(nms_ > 0 && now - ms_[nms_-1].t <= mintime)
	return;

    if(nms_ >= COUNT(ms_)-1) {
	for(int i = 1; i < COUNT(ms_); i++)
	    ms_[i-1] = ms_[i];
	nms_ = COUNT(ms_)-1;
    } else {
    }
    ms_[nms_].x = Fl::event_x();
    ms_[nms_].y = Fl::event_y();
    ms_[nms_].t = now;
    ++nms_;
}

int Fl_Gview::pastMaus( MausSample *tms, float then ) {
    if(nms_ == 0) return 0;
    int i;
    for(i = nms_; --i > 0 && ms_[i].t >= then; )
	;
    tms->x = ms_[i].x;
    tms->y = ms_[i].y;
    tms->t = ms_[i].t;
    return 1;
}   

float Fl_Gview::fps() const {
    return (sumw_ == 0) ? 0 : sumw_ / sumwdt_;
}

void Fl_Gview::rateaccum( float dt ) {
    float w = dt / 0.5;		/* half-second smoothing scale */
    sumwdt_ = w * dt + sumwdt_/(1 + w);
    sumw_ = w + sumw_/(1 + w);
}

static int wasESC;	/* double-ESC counter */

teuben's avatar
teuben committed
static int verbose;	/* for debugging! */

#define	XYBUTTON	FL_BUTTON1
#define	PICKBUTTON	FL_BUTTON2
#define	ZBUTTON		FL_BUTTON3

slevy's avatar
slevy committed
#define XYBUTTONVAL	FL_LEFT_MOUSE
#define	PICKBUTTONVAL	FL_MIDDLE_MOUSE
#define ZBUTTONVAL	FL_RIGHT_MOUSE

teuben's avatar
teuben committed
#define	SLOWKEY		FL_SHIFT
#define	CONSTRAINKEY	FL_CTRL

int Fl_Gview::handle(int ev) {
teuben's avatar
teuben committed
  if(eventhook) {
    // Allow clients to pre-screen our events without subclassing
teuben's avatar
teuben committed
    switch((*eventhook)(this, ev)) {
    case 1: return 1;	// pre-screener handled it
    case -1: return 0;  // pre-screener commands us to ignore it too
    default: break;	// Else just process event normally below
teuben's avatar
teuben committed
    }
  }

  switch(ev) {
  case FL_FOCUS:   hasfocus_ = 1; return 1;  // Yes, we want FL_KEYBOARD events
  case FL_UNFOCUS: hasfocus_ = 0; return 1;

  case FL_PUSH:
	{  // Besides navigating, grab keyboard focus if we're clicked-on
	    if(!hasfocus_) take_focus();
	}
slevy's avatar
slevy committed
	/* and fall into ... */
teuben's avatar
teuben committed
  case FL_RELEASE:

//=========== T. Takahei code added by Marx version 0.7.04 ===========
#ifndef __APPLE__
    if(Fl::event_state(XYBUTTON|ZBUTTON|PICKBUTTON) == 0
       && (Fl::event_button() == XYBUTTONVAL ||
	   Fl::event_button() == ZBUTTONVAL)) {
#else
      //if(Fl::event_state(XYBUTTON) == 0  && (!Fl::event_state(CONSTRAINKEY))) { //original 
      
      //handle a real two or three button mac mouse
      if(Fl::event_state(XYBUTTON|ZBUTTON|PICKBUTTON) == 0 && (Fl::event_button() == XYBUTTONVAL || Fl::event_button() == ZBUTTONVAL)) {
#endif
//============ end T. Takahei code =====================================

slevy's avatar
slevy committed
	    /* If everything's released, ... */
	    do_nav( FL_RELEASE, evslow_, evzaxis_, evconstrain_ );
	    return 1;
	}
	/* else fall through into... */

  case FL_DRAG:
    takeMausSample( ev, 0.125 );


//=========== T. Takahei code modified by Marx version 0.7.04 ===========
#ifndef __APPLE__
teuben's avatar
teuben committed
    if(Fl::event_state(XYBUTTON|ZBUTTON) && !Fl::event_state(PICKBUTTON)) {
      do_nav(ev, Fl::event_state(FL_SHIFT), Fl::event_state(ZBUTTON),
	     Fl::event_state(CONSTRAINKEY));
      return 1;
    } else if(Fl::event_state(PICKBUTTON)
	      && !Fl::event_state(XYBUTTON|ZBUTTON)
	      && pickcb_ != NULL) {
#else
      /* 
      //debug only
      if(Fl::event_state(FL_ALT))
        printf("you pressed option key\n");
      else if(Fl::event_state(FL_META))
	printf("you pressed apple key\n");
      else if(Fl::event_state(FL_CTRL))
	printf("you pressed ctrl key\n");
      else if(Fl::event_state(FL_BUTTON1))
	printf("you pressed left mouse button\n");
      else
	printf("i don't know what key you pressed\n");
      //end debug only
      */

      if(!Fl::event_state(FL_ALT)) { //if option key is pressed skip ahead for the test for mouse button simulation. 
	if(Fl::event_state(XYBUTTON|ZBUTTON) && !Fl::event_state(PICKBUTTON)) {
teuben's avatar
teuben committed
	do_nav(ev, Fl::event_state(FL_SHIFT), Fl::event_state(ZBUTTON),
	       Fl::event_state(CONSTRAINKEY));
teuben's avatar
teuben committed
	return 1;
	}
      }
      //if(Fl::event_state(XYBUTTON) && !Fl::event_state(CONSTRAINKEY)) { original but now we ignore meta and use constrainkey (FL_CTRL) to rotate in orbit mode (hope do_nav knows this!) 
      if(Fl::event_state(XYBUTTON) && !Fl::event_state(FL_META) && !Fl::event_state(PICKBUTTON)  ) { //enter mouse button simulation if meta key and or real middle mouse button are NOT pressed!
	//do_nav(ev, Fl::event_state(FL_SHIFT), Fl::event_state(FL_ALT), Fl::event_state(FL_META)); //toshi's original
	do_nav(ev, Fl::event_state(FL_SHIFT), Fl::event_state(FL_ALT), Fl::event_state(FL_CTRL));
        return 1;
      } else if (Fl::event_state(PICKBUTTON) && pickcb_ != NULL) {
    //} else if (Fl::event_state(XYBUTTON) && Fl::event_state(CONSTRAINKEY) && pickcb_ != NULL) { commented out per Brian Abbott request not to support CTRL key to emulate middle mouse button pick on Apple
#endif
//=========== end T. Takahei code modified by Marx version 0.7.04 ======================================= 
teuben's avatar
teuben committed

	dpickx_ = Fl::event_x();
	dpicky_ = h() - Fl::event_y();

	if(ev == FL_PUSH) {
	    do_pick( dpickx_, dpicky_ );
	    pickneeded_ = 0;
	} else {
	    if(!pickneeded_) {
		pickneeded_ = 1;
		Fl::add_idle( Fl_Gview::idlepick, (void *)this );
	    }
	}
	return 1;
    }
    return 0;

  case FL_ENTER:
    take_focus();
    return 1;

  case FL_KEYBOARD:

    if(Fl::event_text() == NULL || Fl::event_text()[0] == '\0')
	return 0;

    int c = Fl::event_text()[0];

    if(c != '\033')
	wasESC = 0;

teuben's avatar
teuben committed
    if(num.addchar( c ))
	return 1;


    switch(c) {
    case 'w': reset( retarget() ); notify(); redraw(); break;
    case 'r': retarget(); nav( GV_ROTATE ); break;
    case 'p':
    case 'P': if(num.has) retarget();
	      else {
		do_pick( Fl::event_x_root() - x_root(),
			 y_root() + h() - Fl::event_y_root() );
	      }
	      break;
    case 'f': retarget(); nav( GV_FLY ); break;
    case 't': retarget(); nav( GV_TRANSLATE ); break;
    case 'o': retarget(); nav( GV_ORBIT ); break;
    case 'O': perspective( num.value( !perspective() )  );
slevy's avatar
 
slevy committed
	      if(msg) msg("Perspective %s", persp_?"on":"off");
teuben's avatar
teuben committed
	      notify();
	      break;

    case 'v':
    case 'V':	if(num.has) angyfov( num.fvalue() );
		else halfyfov( halfyfov() * (c=='v' ? 1.25 : 1/1.25) );
slevy's avatar
 
slevy committed
		if(msg) msg("angyfov %g", angyfov());
teuben's avatar
teuben committed
		notify();
		break;

    case 'v'&0x1F: verbose = !verbose; break;

    case '@':
teuben's avatar
teuben committed
	vgettranslation( &cpos, To2w( retarget() ) );
	tfm2quat( &cquat, To2w( retarget() ) );
	if(msg) msg("%s at %.4g %.4g %.4g  quat %.4g %.4g %.4g %.4g",
teuben's avatar
teuben committed
		dname( retarget() ),
		cpos.x[0],cpos.x[1],cpos.x[2],
		cquat.q[0], cquat.q[1], cquat.q[2], cquat.q[3]);
teuben's avatar
teuben committed
	break;

    case '=':
	const float *fp;
	int me;
	me = retarget();
	fp = &To2w(me)->m[0];
slevy's avatar
 
slevy committed
	if(msg) msg("%s To2w():", dname(me));
teuben's avatar
teuben committed
	int i;
	for(i = 0; i < 16; i+=4)
slevy's avatar
 
slevy committed
	    if(msg) msg("\t%9.5g %9.5g %9.5g %9.5g", fp[i],fp[i+1],fp[i+2],fp[i+3]);
teuben's avatar
teuben committed
	Matrix w2o;
	eucinv( &w2o, To2w(me) );
	fp = &w2o.m[0];
slevy's avatar
 
slevy committed
	if(msg) msg("%s Tw2o():", dname(me));
teuben's avatar
teuben committed
	for(i = 0; i < 16; i+=4)
slevy's avatar
 
slevy committed
	    if(msg) msg("\t%9.5g %9.5g %9.5g %9.5g", fp[i],fp[i+1],fp[i+2],fp[i+3]);
teuben's avatar
teuben committed
	float aer[3];
	Point xyz;
	tfm2xyzaer( &xyz, aer, To2w(me) );
slevy's avatar
 
slevy committed
	if(msg) {
	    msg("%s o2w = XYZ Ry Rx Rz FOV:", dname(me));
	    msg("  %g %g %g  %g %g %g  %g",
teuben's avatar
teuben committed
		xyz.x[0],xyz.x[1],xyz.x[2],
		aer[1],aer[0],aer[2],
		perspective() ? angyfov() : -2*halfyfov() );
slevy's avatar
 
slevy committed
	}
teuben's avatar
teuben committed
	break;

    case '\033':	  /* ESC */
	if(wasESC++ > 0)
	    exit(0);
teuben's avatar
teuben committed
    // case FL_End: Fl::warning("End key!"); return 1; // test!

    default: c = 0;
    }

    num.clear();

    /* Maybe check Fl::event_key(FL_HOME), etc.? */
    return c==0 ? 0 : 1;

  }
  return 0;
}

void Fl_Gview::idlepick( void *vthis ) {
  Fl_Gview *me = (Fl_Gview *)vthis;

  if(me->pickneeded_) {
    me->do_pick( me->dpickx_, me->dpicky_ );
    me->pickneeded_ = 0;
    Fl::remove_idle( Fl_Gview::idlepick, vthis );
  }
}


void Fl_Gview::notifier( void (*func)(Fl_Gview*,void*), void *arg ) {
  notify_ = func;
  notifyarg_ = arg;
}

void Fl_Gview::notify() {
  if(notify_ != NULL)
    (*notify_)( this, notifyarg_ );
}

void Fl_Gview::nav( enum Gv_Nav newnav ) {
  if(nav_ != newnav) {
    nav_ = newnav;
slevy's avatar
slevy committed
    driftoff();
teuben's avatar
teuben committed
    notify();
  }
}

/*
 * rot through theta about vector {x,y,z}, where tan(theta/2) = length(xyz)
 */
static void rotation(Matrix *T, float x, float y, float z)
{

  float chalf = sqrtf(1 / (1 + x*x + y*y + z*z));
  Quat rq = { chalf, x*chalf, y*chalf, z*chalf };
  quat2tfm(T, &rq);
teuben's avatar
teuben committed
}

static void translation(Matrix *T, float x, float y, float z)
{
  Point p = {x,y,z};
  *T = Tidentity;
  vsettranslation( T, &p );
}

slevy's avatar
slevy committed
void Fl_Gview::idledrift( void *vthis ) {
  Fl_Gview *me = (Fl_Gview *)vthis;
slevy's avatar
slevy committed
  Fl::check();
slevy's avatar
slevy committed
  if(me->drifting_) {
    me->do_nav( FL_DRAG, me->evslow_, me->evzaxis_, me->evconstrain_);
  } else {
    Fl::remove_idle( idledrift, vthis );
  }
}

void Fl_Gview::driftoff() {
  if(drifting_) {
    Fl::remove_idle( idledrift, this );
    drifting_ = 0;
  }
}

void Fl_Gview::drifton( MausSample *rate ) {
  if((rate->x == 0 && rate->y == 0) || rate->t == 0) {
    driftoff();
    return;
  }
  driftrate_ = *rate;
  if(!drifting_) {
    Fl::add_idle( idledrift, this );
    drifting_ = 1;
    drifttime_ = wallclock_time();
  }
}

#define MAXDELAY 1.2
#define MINDELAY 0.15

slevy's avatar
slevy committed
void Fl_Gview::drifton() {
  MausSample rate;
  float now = wallclock_time();
  if(pastMaus(&rate, now - 0.33)) {
    rate.x = Fl::event_x() - rate.x;
    rate.y = Fl::event_y() - rate.y;
    rate.t = now - rate.t;
	/* assume extreme times to be measurement errors */
    if(rate.t > MAXDELAY) rate.t = MAXDELAY;
    else if(rate.t < MINDELAY) rate.t = MINDELAY;
slevy's avatar
slevy committed
    if(fabs(rate.x) > nullthresh_ || fabs(rate.y) > nullthresh_) {
	drifton(&rate);
    } else {
	driftoff();
    }
  } else {
    driftoff();
  }
}


void Fl_Gview::inertia( int on ) {
  inertia_ = on;
  if(!inertia_)
    driftoff();
}
    

teuben's avatar
teuben committed
void Fl_Gview::start_nav( int mytarget ) {
    evx_ = Fl::event_x();
    evy_ = Fl::event_y();
    evTc2w_ = Tc2w_, evTw2c_ = Tw2c_;
    evTobj2w_ = *To2w( mytarget );
}

teuben's avatar
teuben committed
void Fl_Gview::do_nav(int ev, int slow, int zaxis, int constrained) {

  if((slow != evslow_ || zaxis != evzaxis_ || constrained != evconstrain_)
	&& (ev == FL_DRAG)) {
    /* If conditions changed, pretend button was released
     * (so we commit to this nav update) and
     * pushed again.
     */
    do_nav( FL_RELEASE, evslow_, evzaxis_, evconstrain_ );
    ev = FL_PUSH;
  }

  Gv_Nav curnav = (constrained && nav_==GV_ORBIT)
			? (zaxis ? GV_ROTATE : GV_FLY)
			: nav_;

  int mytarget = (curnav == GV_ORBIT || curnav == GV_FLY || !movingtarget())
		? GV_ID_CAMERA
		: target();

  if(ev == FL_PUSH) {
    start_nav( mytarget );
    evslow_ = slow;
    evzaxis_ = zaxis;
    evconstrain_ = constrained;
slevy's avatar
slevy committed
    driftoff();
teuben's avatar
teuben committed
    return;
  }

  if((ev == FL_DRAG || ev == FL_RELEASE) && w() > 0) {
    float slowrate = slow ? 0.05 : 1.0;
    int field = w() > h() ? w() : h();
    float dx = -(Fl::event_x() - evx_);
    float dy =  (Fl::event_y() - evy_);
    float angfield = halfyfov_ / focallen_;

slevy's avatar
slevy committed

    if(drifting()) {
	start_nav(mytarget);
	float now = wallclock_time();
	float tscale = (now - drifttime_) / driftrate_.t;
	drifttime_ = now;
	dx = -driftrate_.x * tscale;
	dy = driftrate_.y * tscale;
teuben's avatar
teuben committed
    }

    dx *= slowrate / field;
    dy *= slowrate / field;

    if(constrained && curnav == nav_) {
	// CTRL key means "constrain to X/Y axis",
	// except in Orbit mode where it means "fly" or "twist"!
	if(fabs(dx) < fabs(dy)) dx = 0;
	else dy = 0;
    }

    Matrix Tincr;
    const Matrix *Tf2w, *Tw2f;
    const Point *pcenterw = NULL;

    if(owncoords_) {
	Tf2w = Tw2f = NULL;
    } else {
	Tf2w = &evTc2w_, Tw2f = &evTw2c_;
    }

    switch(curnav) {
    case GV_ROTATE:
	if(zaxis)
	    rotation(&Tincr, 0, 0, (dx+dy));
	else
	    rotation(&Tincr, -2*dy, 2*dx, 0);
	pcenterw = &pcenw_;
	break;
    case GV_ORBIT:
	if(zaxis) {
	    Point pcamw;
	    vgettranslation( &pcamw, Tc2w() );
	    translation(&Tincr, 0, 0, (dx+dy) * vdist(&pcenw_, &pcamw));
	    start_nav( mytarget );
	} else {
	    rotation(&Tincr, -2*dy, 2*dx, 0);
	}
	pcenterw = &pcenw_;
	break;
    case GV_FLY:
	if(zaxis) {
	    translation(&Tincr, 0, 0, (dx+dy) * zspeed * focallen_);
	} else {
	    rotation(&Tincr, dy*angfield, -dx*angfield, 0);
	}
	pcenterw = NULL;
	break;
    case GV_TRANSLATE:
	if(zaxis) {
	    translation(&Tincr, 0, 0, -(dx+dy) * zspeed * focallen_);
	} else {
	    translation(&Tincr, 2 * dx * halfyfov_ * focallen_, 2 * dy * halfyfov_ * focallen_, 0);
	}
	pcenterw = NULL;
	break;
    default:
	fprintf(stderr, "Fl_Gview::do_nav(): Unknown nav mode %d\n", nav_);
	Tincr = Tidentity;
    }

slevy's avatar
slevy committed
    if(ev == FL_RELEASE) {
	if(inertia())
	    drifton();
	else
	    driftoff();
    }

teuben's avatar
teuben committed
    if(verbose) {
teuben's avatar
teuben committed
	vgettranslation( &dp, &Tincr );
	tfm2quat( &dq, &Tincr );
	if(msg) msg("dx %.3f dy %.3f  angfield %.3g  dp %.4g %.4g %.4g  dq %.4g %.4g %.4g %.4g",
teuben's avatar
teuben committed
		dx, dy, angfield,