#include "viewer3d.hh"

#include "pdgmap.hh"

#include <QMouseEvent>
#include <QTimer>
#include <QTextStream>
#include <QFile>
#include <QApplication>

#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>

#include <GL/glu.h>

//the spheres and tracks are drawn a standard size and then scaled in paintGL
#define BLOB_SIZE 50.
//culling depth for objects
//do not display objects closer than this
#define DEPTH_MIN 1.
//or further away than this
#define DEPTH_MAX 100000.
//numberical id's for opengl objects
//lowest id's are used for tracks
#define TRACK_OFFSET 100
#define DETECTOR_OFFSET 100000000
#define HIT_OFFSET (DETECTOR_OFFSET+100000)

//3d image widget derived from GL capable widget
Viewer3D::
Viewer3D(QWidget* parent):
  QGLWidget(parent),
  ViewerBase()
{

  capture_=false;
  open_=false;

  setMinimumSize(800,600);
  //by default, mouse does x-y shift
  action_=MOVE;
  //origin sphere default size
  origSize_=50;
  //and position
  origPosn_=DEFORIG;
  //not yet generated
  glOrigin_=glDummy_=-1;

  //what to show
  showFullDet_=false;
  showWireFrame_=true;

  //background colour
  bgRed_=bgGreen_=bgBlue_=0.6;

  //accept focus to pick up keyboard events
  //(these are picked up by Gui
  setFocusPolicy(Qt::StrongFocus);
  //catch mouse movement event if no button is pressed down
  setMouseTracking(true);

  //apply initial conditions
  init();
  //default shape opacity
  opacity_=0.3;

}

//mouse action by user
//press button
void Viewer3D::
mousePressEvent(QMouseEvent* e)
{
  //if holding down left button
  if (e->button()==Qt::LeftButton)
    {
      //note current mouse position
      mousex_=e->x();
      mousey_=e->y();
    }
  //if the right button, we are going to want to see if it was on a track
  else if (e->button()==Qt::RightButton)
    {
      int track=clickfindGL(e->x(), e->y());
      if (track >= DETECTOR_OFFSET && track < HIT_OFFSET)
	{
	  //tell any listening functions that track selection has taken place
	  //if the click was outside the tracks, "track" will be zero, otherwise track is number
	  emit select(track-DETECTOR_OFFSET, THD_DETECTOR);
	}
      else if (track>=HIT_OFFSET && track-HIT_OFFSET<hits_.size())
	{
	  emit select(track-HIT_OFFSET, THD_HIT);
	}
      else if (track>=TRACK_OFFSET && track-TRACK_OFFSET<tracks_.size())
	{
	  emit select(track-TRACK_OFFSET, THD_TRACK);
	}
    }

}
//move mouse
void Viewer3D::
mouseMoveEvent(QMouseEvent* e)
{
  //if holding down left button
  if (e->buttons()&Qt::LeftButton)
    {
      //this is where we have moved to
      int newx=e->x();
      int newy=e->y();
      int deltax=newx-mousex_;
      int deltay=newy-mousey_;
      //add move to list of geometry moves
      addMove(deltax, deltay);
      //note current mouse position for next move
      mousex_=newx;
      mousey_=newy;
    }
  //no mouse button is down
  else if (e->buttons()==Qt::NoButton)
    {
      pointerInfo(e->x(), e->y());
    }
}
void Viewer3D::
pointerInfo(int x, int y)
{
  //create new "information" string for bottom right of display
  QString newinfo;
  //if pointing at a detector component, the name
  int id=clickfindGL(x, y);
  if (id>=HIT_OFFSET)
    {
      const Hit& hit=hits_.at(id-HIT_OFFSET);
      newinfo = "Hit: "+QString::number(hit.value, 'f', 2)+" MeV";
    }
  else if (id >= DETECTOR_OFFSET)
    newinfo = "Detector: "+detector_.at(id - DETECTOR_OFFSET).name;
  else if (id>=TRACK_OFFSET)
    {
      const Track& track=tracks_.at(id-TRACK_OFFSET);
      newinfo = "Track: "+QString::number(track.value, 'f', 2)+" MeV "+pdgstring(track.pdgcode).second;
    }
  //if this information has changed since last time
  if (newinfo!=mouseInfo_)
    {
      //display the new information
      mouseInfo_=newinfo;
      paintGL();
      updateGL();
    }

}
 void Viewer3D::
   mouseDoubleClickEvent(QMouseEvent* e)
 {
   //if the right button, we are going to want to see if it was on a track
   if (e->button()==Qt::RightButton)
    {
      int track=clickfindGL(e->x(), e->y());
      if (track >= DETECTOR_OFFSET && track < HIT_OFFSET)
	{
	  //tell any listening functions that track selection has taken place
	  //if the click was outside the tracks, "track" will be zero, otherwise track is number
	  emit hide(track-DETECTOR_OFFSET);
	}
    }
 }

//find location where right click took place
int Viewer3D::
clickfindGL(int x, int y)
{
  GLuint buff[5120]={0};
  GLint hits, viewport[4];
  //get the display width/height etc
  glGetIntegerv(GL_VIEWPORT, viewport);
  //create buffer to hold information on objects consistent with click location
  glSelectBuffer(5120, buff);
  //switch to gl mode for finding objects
  glRenderMode(GL_SELECT);
  //initialise name list so that object numbers will be included in buffer
  glInitNames();
  //re-initialise the perspective etc as in resizeGL
  glMatrixMode(GL_PROJECTION);
  //back-up the normal display settings
  glPushMatrix();
  //go to gl standard display settings
  glLoadIdentity();
  //define region to look for objects in
  gluPickMatrix(GLdouble(x), GLdouble(viewport[3]-y-1), 1.0, 1.0, viewport);
  //gluPickMatrix(GLdouble(width()/2.), GLdouble(height()/2.), 30.0, 30.0, viewport);
  //re-initialise the perspective etc as in resizeGL
  GLfloat w = (float) width() / (float) height();
  GLfloat h = 1.0;
  glFrustum(-w, w, -h, h, DEPTH_MIN, DEPTH_MAX);
  //perform standard rendering of objects exactly as when displaying on screen
  paintGL();
  //revert to display settings from back-up (glPushMatrix above)
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  //switch back to drawing mode
  //as we do so, opengl gives us the number of shapes it thinks we may have clicked on, and fills buff with the details
  hits=glRenderMode(GL_RENDER);
//look for the front track from the list of candidates
  unsigned id=0;
  float zmin=2000000000;
  GLuint* ptr=buff;
  //go through all cansisates
  //std::cout << hits << std::endl;
  for (int i=0; i<hits; i++)
    //if this is a track and either it's the first, or it's closer than the one we have
    //    if (buff[i*4+3]>0)
      {
	//assume that this is the track we're after
	unsigned num=*ptr;
	ptr++;
	float z1=(float)*ptr/0x7fffffff;
	ptr++;
	//	float z2=(float)*ptr/0x7fffffff;
	ptr++;
	float z=z1;
	for (int j=0; j<num; j++)
	  {
	    if (z>1)
	      {
	    int t=*ptr;
	    //	    if (t>=HIT_OFFSET)
	    //	      std::cout << (t-HIT_OFFSET) << " " << z1 << " " << z2 << std::endl;
	    //we select this object if one of the following is true
	    //1  nothing is selected
	    //2  we've currently got a detector component selected and this is a hit or a track
	    //3  we've currently got a hit or track selected and this is a closer hit or track
	    //4  we've currently got a detector component selected and this is a closer detector component
	    //	    std::cout << i << "  considering object " << t << ": " << (t<DETECTOR_OFFSET?"track":(t>=HIT_OFFSET?"hit":"detector")) << " with z=" << z << std::endl;
	    if (t>0)
	      {
		//std::cout << "id " << t << " z = " << z << std::endl;
		if (!id || //nothing selected
		    (z < zmin && (id>=DETECTOR_OFFSET && id<HIT_OFFSET)) || //closer and existing selection detector component
		    ((t<DETECTOR_OFFSET || t>=HIT_OFFSET) && (id>=DETECTOR_OFFSET && id<HIT_OFFSET)) || //existing is detector component and this is not 
		    (z<zmin && (t<DETECTOR_OFFSET || t>=HIT_OFFSET) && (id<DETECTOR_OFFSET || id>=HIT_OFFSET)) //existing not detector component, neither is this, and it's closer
		    )
		  {
		  //		std::cout << "selected\n";
		  zmin=z;
		  id=t;
		}
	      }
	      }
	    ptr++;
	  }
	    //	    else
	    //	      std::cout << "rejected\n";
	//	    ptr+=num;

      }
//    if (id>=HIT_OFFSET)
//      std::cout << "selected hit " << (id-HIT_OFFSET) << std::endl;
//    else if (id>=DETECTOR_OFFSET)
//      std::cout << "selected detector component " << (id-DETECTOR_OFFSET) << std::endl;
//    else if (id>=TRACK_OFFSET)
//      std::cout << "selected track " << (id-TRACK_OFFSET) << std::endl;
//    else
//      std::cout << "no hit or detector componet identified\n";
  return id;

}
//find location where right click took place
void Viewer3D::
zOrderGL(void)
{
  //  timeval tv1, tv2;
  //gettimeofday(&tv1, NULL);
  //detector object ordering map
  detOrder_.clear();
  //set up gl buffer
  GLuint buff[102400]={0};
  GLint hits;//, viewport[4];
  //get the display width/height etc
  //glGetIntegerv(GL_VIEWPORT, viewport);
  //create buffer to hold information on objects consistent with click location
  glSelectBuffer(102400, buff);
  //switch to gl mode for finding objects
  glRenderMode(GL_SELECT);
  //initialise name list so that object numbers will be included in buffer
  glInitNames();
  //re-initialise the perspective etc as in resizeGL
  //glMatrixMode(GL_PROJECTION);
  //back-up the normal display settings
  //glPushMatrix();
  //go to gl standard display settings
  //glLoadIdentity();
  //define region to look for objects in
  //int x=376;
  //int y=119;
  //x=300;y=200;
  //gluPickMatrix(GLdouble(x), GLdouble(y), 200., 200., viewport);
  //gluPickMatrix(GLdouble(width()/2.), GLdouble(height()/2.), 30.0, 30.0, viewport);
  //re-initialise the perspective etc as in resizeGL
  //GLfloat w = (float) width() / (float) height();
  //GLfloat h = 1.0;
  //glFrustum(-w, w, -h, h, DEPTH_MIN, DEPTH_MAX);
  //perform standard rendering of objects exactly as when displaying on screen
  paintGL();
  //revert to display settings from back-up (glPushMatrix above)
  //glMatrixMode(GL_PROJECTION);
  //glPopMatrix();
  //switch back to drawing mode
  //as we do so, opengl gives us the number of shapes it thinks we may have clicked on, and fills buff with the details
  hits=glRenderMode(GL_RENDER);
  //cout << hits << " hits\n";
//look for the front track from the list of candidates
//  int track=-1;
//  unsigned zmin;
  GLuint* ptr=buff;
  //go through all cansisates
  for (int i=0; i<hits; i++)
    //if this is a track and either it's the first, or it's closer than the one we have
    //    if (buff[i*4+3]>0)
      {
	//assume that this is the track we're after
	//zmin=buff[i*4+1];
	//int track=buff[i*4+3];
	//	cout << "hit " << i << endl;
	int names=*ptr;
	//	cout << "   #names " << (*ptr) << endl;
	ptr++;
	//	cout << "   z1 " << ((float)(*ptr)/0x7fffffff) << endl;
	float z=(float)*ptr/0x7fffffff;
	ptr++;
	//	cout << "   z2 " << ((float)(*ptr)/0x7fffffff) << endl;
	ptr++;
	//	cout << "   name list:" << endl;
	for (int j=0; j<names; j++)
	  {
	    for (int j = 0; j < names; j++) {
	      if (!j && (*ptr) >= DETECTOR_OFFSET && (*ptr) < HIT_OFFSET)
		detOrder_.push_back(ZOrder((*ptr) - DETECTOR_OFFSET, z));
	    //	    cout << "      " << (*ptr) << endl;
	    ptr++;
	  }
	//	cout << endl;
      }
      }
  std::sort(detOrder_.begin(), detOrder_.end());
  //  gettimeofday(&tv2, NULL);
  //   cout << "z ordering took " << (tv2.tv_usec-tv1.tv_usec) << " microseconds\n";
//     cout << "detector ordering contains " << detorder.size() << " points\n";
//    cout << "number of detector components is " << detector.size() << endl;
//   cout << "detector ordering is as follows\n";
  //  for (int i=0; i<detorder.size(); i++)
  //    cout << detorder[i].id << "   " << detorder[i].z << endl;
}
//key press
void Viewer3D::
keyPressEvent(QKeyEvent* e)
{
//   //if shift, ctrl, alt etc
//   if (e->key()!=Qt::NoButton && e->key()!=Qt::Keypad)
//     //ignore
//     return;
  //calibration - match to a mouse move of this many pixels
  const int KEY2PIXELS=10;

  //arrow keys, same effect as mouse
  if (e->key()==Qt::Key_Left)
    {
      //add appropriate move
      addMove(-KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_Right)
    {
      //add appropriate move
      addMove(KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_Up)
    {
      //add appropriate move
      addMove(0, -KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_Down)
    {
      //add appropriate move
      addMove(0, KEY2PIXELS);
    }
  //different keys will apply different navigation steps 
  //for these keys match the navigation mode to the key
  else if (e->key()==Qt::Key_A)
    {
      //a will be move left
      //set navigation mode accordingly
      setAction(MOVE);
      emit modeChange(MOVE);
      //add appropriate move
      addMove(KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_D)
    {
      //move right
      //set navigation mode accordingly
      emit modeChange(MOVE);
      setAction(MOVE);
      //add appropriate move
      addMove(-KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_S)
    {
      //move down
      //set navigation mode accordingly
      emit modeChange(MOVE);
      setAction(MOVE);
      //add appropriate move
      addMove(0, -KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_W)
    {
      //move up
      //set navigation mode accordingly
      emit modeChange(MOVE);
      setAction(MOVE);
      //add appropriate move
      addMove(0, KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_Y)
    {
      //zoom in
      //set navigation mode accordingly
      emit modeChange(ZOOM);
      setAction(ZOOM);
      //add appropriate move
      addMove(0, -KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_H)
    {
      //zoom out
      //set navigation mode accordingly
      emit modeChange(ZOOM);
      setAction(ZOOM);
      //add appropriate move
      addMove(0, KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_F)
    {
      if (e->modifiers() & Qt::ControlModifier)
	emit fullScreen();
      //pan down
      //set navigation mode accordingly
      emit modeChange(PAN);
      setAction(PAN);
      //add appropriate move
      addMove(0, KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_R)
    {
      //pan up
      //set navigation mode accordingly
      emit modeChange(PAN);
      setAction(PAN);
      //add appropriate move
      addMove(0, -KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_E)
    {
      //pan left
      //set navigation mode accordingly
      emit modeChange(PAN);
      setAction(PAN);
      //add appropriate move
      addMove(-KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_T)
    {
      //pan right
      //set navigation mode accordingly
      emit modeChange(PAN);
      setAction(PAN);
      //add appropriate move
      addMove(KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_G)  //american for full stop
    {
      //twist left
      //set navigation mode accordingly
      emit modeChange(ZOOM);
      setAction(ZOOM);
      //add appropriate move
      addMove(-KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_J) //proper slash, not backslash
    {
      //twist right
      //set navigation mode accordingly
      emit modeChange(ZOOM);
      setAction(ZOOM);
      //add appropriate move
      addMove(KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_I)
    {
      //move origin out
      //set navigation mode accordingly
      emit modeChange(ORIGIN);
      setAction(ORIGIN);
      //add appropriate move
      addMove(0, -KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_K)
    {
      //move origin in
      //set navigation mode accordingly
      emit modeChange(ORIGIN);
      setAction(ORIGIN);
      //add appropriate move
      addMove(0, KEY2PIXELS);
    }
  else if (e->key()==Qt::Key_O) //square bracket
    {
      //mke origin blob bigger
      //set navigation mode accordingly
      emit modeChange(ORIGIN);
      setAction(ORIGIN);
      //add appropriate move
      addMove(KEY2PIXELS, 0);
    }
  else if (e->key()==Qt::Key_U)  //square bracket
    {
      //make origin blob smaller
      //set navigation mode accordingly
      emit modeChange(ORIGIN);
      setAction(ORIGIN);
      //add appropriate move
      addMove(-KEY2PIXELS, 0);
    }
}


//set up GL lighting and other basics
void Viewer3D::
initializeGL(void)
{
  //background colour
  glClearColor(bgRed_, bgGreen_, bgBlue_, 0.0);
  //enable transparency
  glEnable (GL_BLEND); 
  //transparency algorithm
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  //disable backface culling
  glDisable(GL_CULL_FACE);
  glFrontFace(GL_CW);
  //fire up basic surface lighting
  glEnable(GL_LIGHTING);

  //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  //glEnable(GL_COLOR_MATERIAL);

  // Create light components
  //first 3 numbers are colout, last is kind of strength (0-1)
  GLfloat ambientLight[] = { 0.0f, 0.0f, 0.0f, 0.5f };
  GLfloat diffuseLight[] = { 0.05f, 0.05f, 0.05f, 0.05f };
  //  GLfloat specularLight[] = { 0.5f, 0.5f, 0.5f, 0.5f };
  //position of light: x, y, z, be positional (1 for true)
  GLfloat position[] = { -100000., 0., 0., 1.};

  //lighting
  //this is a bit of a dark art
  // Assign created components to GL_LIGHT0
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
  //  glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glEnable(GL_LIGHT0);
  position[0]=0.;
  position[2]=100000.;
  glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuseLight);
  //  glLightfv(GL_LIGHT1, GL_SPECULAR, specularLight);
  glLightfv(GL_LIGHT1, GL_POSITION, position);
  glEnable(GL_LIGHT1);

  //ensure that foreground objects appear in front of background ones
  glEnable( GL_DEPTH_TEST );

  //prepare cross-hair
  //just 2 lines in the middle of the screen
  GLfloat crosshaircol[]={0., 0., 0., 1.};
  //a new opengl object, gets an integer address
  glCrossHair_=glGenLists(1);
  glNewList(glCrossHair_, GL_COMPILE);
  //set colour
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, crosshaircol);
  //see function, this draws the lines
  glCrossHair();
  //end of this gl object
  glEndList();

  //similarly generate origin sphere
  //the blob which appears when navigation 
  GLfloat centrecol[4] = {0.3, 0.3, 0.3, 1.0 };
  glOrigin_=glGenLists(1);
  glNewList(glOrigin_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, centrecol);
  //see function
  glBlob();
  glEndList();	

  //this is so that the system has something to draw at startup otherwise gl mis-behaves
  GLfloat nothingcol[4] = {0.0, 0.0, 0.0, 0.0 };
  glDummy_=glGenLists(1);
  glNewList(glDummy_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, nothingcol);
  glBlob();
  glEndList();	

 // colour for normal blob
  GLfloat blobcol[4] = {0.5, 1.0, 0.5, 0.75};
  // for highlighted blob
  GLfloat blobselcol[4] = {1.0, 0.5, 0.5, 0.75};

  // create blob
  glSphere_ = glGenLists(1);
  glNewList(glSphere_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blobcol);
  glBlob();
  glEndList();

  glWireSphere_ = glGenLists(1);
  glNewList(glWireSphere_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blobcol);
  glBlob(true);
  glEndList();

  // selected blob
  glSelSphere_ = glGenLists(1);
  glNewList(glSelSphere_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, blobselcol);
  glBlob();
  glEndList();

  //colour for normal cylinder
  GLfloat cylindercol[4] = {1.0, 0.0, 1.0, 0.75};
  //highlighted cylindeer
  GLfloat cylinderselcol[4] = {1.0, 0.5, 0.5, 0.75};
  //create cylinder
  glCylinder_=glGenLists(1);
  glNewList(glCylinder_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cylindercol);
  glCylinder();
  glEndList();
  glWireCylinder_=glGenLists(1);
  glNewList(glWireCylinder_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cylindercol);
  glCylinder(true);
  glEndList();
  
  //selected cylinder
  glSelCylinder_=glGenLists(1);
  glNewList(glSelCylinder_, GL_COMPILE);
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cylinderselcol);
  glCylinder();
  glEndList();

}

//window resized
void Viewer3D::
resizeGL( int width, int height )
{
    // setup the size of the window to draw in... we want to use the entire window
    glViewport(0, 0, width, height);
    
    // setup the projection matrix.
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //perspective and depth of field
    GLfloat w = (float) width / (float) height;
    GLfloat h = 1.0;
    glFrustum(-w, w, -h, h, DEPTH_MIN, DEPTH_MAX);
    glEnable(GL_DEPTH);    


}

//window needs re-painting
void Viewer3D::
paintGL(void)
{
  for (int i=0; i<detector_.size(); i++)
    {
      glShape& shape=detector_.at(i);
      if (shape.glWire<0)
	renderShape(shape);
    }

  //reset painter
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  //switch to drawing mode
  glMatrixMode(GL_MODELVIEW);
  //reset translation matrix
  glLoadIdentity();

    //track net translations
  Move tot;
  memset(&tot, 0, sizeof(Move));
//     tot.x+=INITX;
//     tot.y+=INITY;
//     tot.z+=INITZ;

    //move into position for crosshair
    glTranslatef(0, 0, -100);
    //draw croshair if in x-y translation mode
    if (action_==MOVE)
      glCallList(glCrossHair_);
    //move back to effect chosen position of rotation vertex
    glTranslatef(0, 0, -(origPosn_-100));
        
  
    //if we are working on moving the origin
    //draw origin
    if (action_==ORIGIN)
      {
	//adjust scale to effect proper shpere size
	glScalef(origSize_/BLOB_SIZE, origSize_/BLOB_SIZE, origSize_/BLOB_SIZE);
	//draw standard sphere
	glCallList(glOrigin_); 
	//put scale back
	glScalef(BLOB_SIZE/origSize_, BLOB_SIZE/origSize_, BLOB_SIZE/origSize_);
      }
    else
      {
	//draw the invisible sphere mentioned earlier to avoid lighting problem when there are no spheres present
	glScalef(1e-3, 1e-3, 1e-3);
	glCallList(glDummy_);
	glScalef(1e3, 1e3, 1e3);
      }


    //in order to properly compound rotations and the translations interspersed between them, the rotation axes must be changed after each rotation and following motion must be applied to the rotated axes
    //this is a bitch
    double xax[3]={1,0,0};
    double yax[3]={0,1,0};
    double zax[3]={0,0,1};
    //go through each rotation and translation
    for (std::vector<Move>::iterator it=moves_.begin(); it!=moves_.end(); it++)
      {
	//if this one is a rotation
	if (it->isRot)
	  {
	    double cang;
	    double sang;
	    double r[3][3];
	    double n[3];
	    //if it is a rotation about the x axis
	    if (fabs(it->x)>fabs(it->y) && fabs(it->x)>fabs(it->z))
	      {
		//apply rotation to world
		glRotatef(it->x, xax[0], xax[1], xax[2]);
		//apply rotation to axes
		cang=cos(it->x*M_PI/180.);
		sang=sin(it->x*M_PI/180.);
		r[0][0]=xax[0];
		r[0][1]=xax[1];
		r[0][2]=xax[2];
		r[1][0]=yax[0]*cang-zax[0]*sang;
		r[1][1]=yax[1]*cang-zax[1]*sang;
		r[1][2]=yax[2]*cang-zax[2]*sang;
		r[2][0]=yax[0]*sang+zax[0]*cang;
		r[2][1]=yax[1]*sang+zax[1]*cang;
		r[2][2]=yax[2]*sang+zax[2]*cang;
		n[0]=sqrt(r[0][0]*r[0][0]+r[0][1]*r[0][1]+r[0][2]*r[0][2]);
		n[1]=sqrt(r[1][0]*r[1][0]+r[1][1]*r[1][1]+r[1][2]*r[1][2]);
		n[2]=sqrt(r[2][0]*r[2][0]+r[2][1]*r[2][1]+r[2][2]*r[2][2]);
		xax[0]=r[0][0]/n[0];
		xax[1]=r[0][1]/n[0];
		xax[2]=r[0][2]/n[0];
		yax[0]=r[1][0]/n[1];
		yax[1]=r[1][1]/n[1];
		yax[2]=r[1][2]/n[1];
		zax[0]=r[2][0]/n[2];
		zax[1]=r[2][1]/n[2];
		zax[2]=r[2][2]/n[2];
	      }
	    //rotation about y
	    else if (fabs(it->y)>fabs(it->x) && fabs(it->y)>fabs(it->z))
	      {
		//to world
		glRotatef(it->y, yax[0], yax[1], yax[2]);
		//to axes
		cang=cos(it->y*M_PI/180.);
		sang=sin(it->y*M_PI/180.);
		r[0][0]=xax[0]*cang+zax[0]*sang;
		r[0][1]=xax[1]*cang+zax[1]*sang;
		r[0][2]=xax[2]*cang+zax[2]*sang;
		r[1][0]=yax[0];
		r[1][1]=yax[1];
		r[1][2]=yax[2];
		r[2][0]=-xax[0]*sang+zax[0]*cang;
		r[2][1]=-xax[1]*sang+zax[1]*cang;
		r[2][2]=-xax[2]*sang+zax[2]*cang;
		n[0]=sqrt(r[0][0]*r[0][0]+r[0][1]*r[0][1]+r[0][2]*r[0][2]);
		n[1]=sqrt(r[1][0]*r[1][0]+r[1][1]*r[1][1]+r[1][2]*r[1][2]);
		n[2]=sqrt(r[2][0]*r[2][0]+r[2][1]*r[2][1]+r[2][2]*r[2][2]);
		xax[0]=r[0][0]/n[0];
		xax[1]=r[0][1]/n[0];
		xax[2]=r[0][2]/n[0];
		yax[0]=r[1][0]/n[1];
		yax[1]=r[1][1]/n[1];
		yax[2]=r[1][2]/n[1];
		zax[0]=r[2][0]/n[2];
		zax[1]=r[2][1]/n[2];
		zax[2]=r[2][2]/n[2];
	      }
	    //about z
	    else if (fabs(it->z)>fabs(it->x) && fabs(it->z)>fabs(it->y))
	      {
		//to worldf
		glRotatef(it->z, zax[0], zax[1], zax[2]);
		//to axes
		cang=cos(it->z*M_PI/180.);
		sang=sin(it->z*M_PI/180.);
		r[0][0]=xax[0]*cang-yax[0]*sang;
		r[0][1]=xax[1]*cang-yax[1]*sang;
		r[0][2]=xax[2]*cang-yax[2]*sang;
		r[1][0]=xax[0]*sang+yax[0]*cang;
		r[1][1]=xax[1]*sang+yax[1]*cang;
		r[1][2]=xax[2]*sang+yax[2]*cang;
		r[2][0]=zax[0];
		r[2][1]=zax[1];
		r[2][2]=zax[2];
		n[0]=sqrt(r[0][0]*r[0][0]+r[0][1]*r[0][1]+r[0][2]*r[0][2]);
		n[1]=sqrt(r[1][0]*r[1][0]+r[1][1]*r[1][1]+r[1][2]*r[1][2]);
		n[2]=sqrt(r[2][0]*r[2][0]+r[2][1]*r[2][1]+r[2][2]*r[2][2]);
		xax[0]=r[0][0]/n[0];
		xax[1]=r[0][1]/n[0];
		xax[2]=r[0][2]/n[0];
		yax[0]=r[1][0]/n[1];
		yax[1]=r[1][1]/n[1];
		yax[2]=r[1][2]/n[1];
		zax[0]=r[2][0]/n[2];
		zax[1]=r[2][1]/n[2];
		zax[2]=r[2][2]/n[2];
	      }
	  }
	//translation
	else
	  {
	    //calculate rotation applied to current axes
	    double tx, ty, tz;
	    tx=it->x*xax[0]+it->y*yax[0]+it->z*zax[0];
	    ty=it->x*xax[1]+it->y*yax[1]+it->z*zax[1];
	    tz=it->x*xax[2]+it->y*yax[2]+it->z*zax[2];
	    //add to total translation
	    tot.x+=tx;
	    tot.y+=ty;
	    tot.z+=tz;
	  }
      }

    //    std::cout << "rotation: for 0,0,1 " << zax[0] << "," << zax[1] << "," << zax[2] << std::endl;
    //std::cout << "theta: " << (atan2(zax[1], zax[0])*180/M_PI) << "   phi: " << (atan2(zax[2], sqrt(zax[0]*zax[0]+zax[1]*zax[1]))*180/M_PI) << std::endl;
    //apply net transform
    glTranslatef(tot.x, tot.y, tot.z);

//     //direction of view
//     float rx=xax[0];
//     float ry=yax[0];
//     float rz=zax[0];
//     float rr=sqrt(rx*rx+ry*ry+rz*rz);

    //draw hits
    if (showHits_)
      {
	double scale=pow(hitScale_, 0.33333)*.75;
	int hitcount=0;
	bool selany=false;
	for (HitIndex i=0; i<hits_.size() && !selany; i++)
	  selany = selany || hits_[i].selected;
	for (HitIndex i=0; i<hits_.size(); i++)
	  {
	    Hit& b=hits_[i];
	    if (b.show)
	      {
	    double diameter=b.value*scale*2.;
	    double dx=b.x-tot.x;
	    double dy=b.y-tot.y;
	    double dz=b.z-tot.z;
	    double r=sqrt(dx*dx+dy*dy+dz*dz);
	    //	    std::cout << diameter << " " << r << " " << (r/diameter) << std::endl;
	    if (r/diameter<100000)
	      {
		hitcount++;
		// note where we are now
		glPushMatrix();
		// move into hit position
		glTranslatef(b.x, b.y, b.z);
//  		double t=atan2(ry, rx)*180./M_PI;
// 		double p=atan2(rz, rr)*180./M_PI;
		glScalef(b.value*scale/BLOB_SIZE,b.value*scale/BLOB_SIZE,
			 b.value*scale/BLOB_SIZE);
// 		glRotatef(-t, 0.0, 0.0, 1.0);
// 		glRotatef(-p, 0.0, 1.0, 0.0);
		glPushName(i + HIT_OFFSET);
		if (b.selected)
		  glCallList(glSelSphere_);
		else if (selany && open_)
		  glCallList(glWireSphere_);
		else
		  glCallList(glSphere_);
		glPopName();
		glScalef(BLOB_SIZE/b.value/scale,BLOB_SIZE/b.value/scale,
			 BLOB_SIZE/b.value/scale);
		// move back
		glPopMatrix();
	      }
	  }
	  }
      }

    //draw tracks
    if (showTracks_)
      {
	int trackcount=0;
	bool selany=false;
	for (TrackIndex i=0; i<tracks_.size() && !selany; i++)
	  selany=selany || tracks_[i].selected;
	for (TrackIndex i=0; i<tracks_.size(); i++)
	  {
	    Track& t=tracks_[i];
	    if (t.show)
	      {
	    // note where we are now
	    glPushMatrix();
	    double dx=t.start.x-tot.x;
	    double dy=t.start.y-tot.y;
	    double dz=t.start.z-tot.z;
	    double diststart=sqrt(dx*dx+dy*dy+dz*dz);
	    dx=t.end.x-tot.x;
	    dy=t.end.y-tot.y;
	    dz=t.end.z-tot.z;
	    double distend=sqrt(dx*dx+dy*dy+dz*dz);
	    double dist=diststart;
	    //calculate length
	    dx=t.start.x-t.end.x;
	    dy=t.start.y-t.end.y;
	    dz=t.start.z-t.end.z;
	    double r=sqrt(dx*dx+dy*dy+dz*dz);
	    //scaling
	    double scale=pow(trackScale_/r/M_PI, 0.5);
	    double diameter=t.delta*scale*2;
	    if (distend<diststart)
	      dist=distend;
	    if ((dist/diameter<5000 || dist/r<5000) && dist/diameter<50000 && dist/r<50000)
	      {
		trackcount++;
		// move into track position
		glTranslatef(t.start.x+(t.end.x-t.start.x)/2, 
			     t.start.y+(t.end.y-t.start.y)/2, 
			     t.start.z+(t.end.z-t.start.z)/2);
		//rotate
		//rotate about z by theta and around y by phi
		double r2=sqrt(dx*dx+dy*dy);

		//new rotation
		//double theta=atan2(dx, dy)*180./M_PI;
		//double phi=atan2(dz, r2)*180./M_PI;
		//old rotation
		double theta=acos((t.end.x-t.start.x)/r2)*180./M_PI;
		double phi=atan2(t.end.z-t.start.z, r2)*180./M_PI;
		//std::cout << dx << ", " << dy << ", " << dz << ": " << theta << ", " << phi << std::endl;
		//perform rotation (new)
		//		glRotatef(-phi, 0.0, 1.0, 0.0);
		//glRotatef(-theta, 0.0, 0.0, 1.0);
		//perform rotation (old)
		glRotatef(-theta, 0.0, 0.0, 1.0);
		glRotatef(-phi, 0.0, 1.0, 0.0);
		//scale
		glScalef(r,t.delta*scale, t.delta*scale);
		//draw
		glPushName(i+TRACK_OFFSET);
		if (t.selected)
		  glCallList(glSelCylinder_);
		else if (selany && open_)
		  glCallList(glWireCylinder_);
		else
		  glCallList(glCylinder_);
		glPopName();
		//reverse scale
		glScalef(1./r, 1./t.delta/scale, 1./t.delta/scale);
		//glRotatef(-theta, 0.0, 0.0, 1.0);	
		//glRotatef(-phi, 0.0, 1.0, 0.0);
		// move back
		glPopMatrix();
	      }
	      }
	  }
      }
    //then detector
    //first wire frame
    if (showWireFrame_ && detector_.size())
      {
	for (long long i=detector_.size()-1; i>=0; i--)
	  {
	    glShape& s=detector_.at(i);
	    glPushName(i+DETECTOR_OFFSET);
	    if (showWireFrame_ && s.show)
	      {
		glCallList(s.glWire);
	      }
	    glPopName();
	  }
      }
    //then solids
    if (showFullDet_)
      {
	if (detOrder_.size())
	  {
	    for (glShapeIndex i=0; i<detOrder_.size(); i++)
	      {
		if (detOrder_.at(i).id<detector_.size())
		  {
		    glShape& s=detector_.at(detOrder_.at(i).id);
		    glPushName(detOrder_.at(i).id+DETECTOR_OFFSET);
		    if (s.show)
		      {
			glCallList(s.glFull);
		      }
		    glPopName();
		  }
	      }
	  }
	else if (detector_.size())
	  {
	    //draw detector
	    for (long long i=detector_.size()-1; i>=0; i--)
	      {
		glShape& s=detector_.at(i);
		glPushName(i+DETECTOR_OFFSET);
		if (s.show)
		  {
		    glCallList(s.glFull);
		  }
		glPopName();
	      }
	  }
      }

    //write our current position etc to screen
    //lights must be turned off for writing
    glDisable(GL_LIGHTING);
//     int fx=tot.x*xax[0]+tot.y*yax[0]+(tot.z-origPosn_)*zax[0];
//     int fy=tot.x*xax[1]+tot.y*yax[1]+(tot.z-origPosn_)*zax[1];
//     int fz=tot.x*xax[2]+tot.y*yax[2]+(tot.z-origPosn_)*zax[2];
    int fx=origPosn_*zax[0]-tot.x;
    int fy=origPosn_*zax[1]-tot.y;
    int fz=origPosn_*zax[2]-tot.z;
	    //    cout << "position: " << fx << ", " << fy << ", " << fz << endl;
    //QString posn=QString("position: ")+QString::number(-(int)tot.x)+QString(", ")+QString::number(-(int)tot.y)+QString(", ")+QString::number(-(int)tot.z-origPosn_);
    QString posn=QString("position: ")+QString::number(fx)+QString(", ")+QString::number(fy)+QString(", ")+QString::number(fz);
    glColor3f(0.,0.,0.);
    renderText(10, 20, posn);
    posn=QString("origin: ")+QString::number(origPosn_);
    renderText(width()-fontMetrics().width(posn)-10, 20, posn);


    //QString theta=QString::number(acos(rx/sqrt(rx*rx+ry*ry))*180./M_PI, 'f', 1);
    //QString phi=QString::number(asin(sqrt(rx*rx+ry*ry)/rr)*180./M_PI, 'f', 1);
    QString theta=QString::number(atan2(zax[1], zax[0])*180/M_PI, 'f', 1);
    QString phi=QString::number(acos(sqrt(zax[0]*zax[0]+zax[1]*zax[1])/sqrt(zax[0]*zax[0]+zax[1]*zax[1]+zax[2]*zax[2]))*180/M_PI, 'f', 1);
    QString thetastr(QChar(0x03d1));
    QString phistr(QChar(0x03c6));
    phistr+="=";
    thetastr+="=";
    QString degstr(QChar(0x00b0));

    QString dirn=thetastr+theta+degstr+QString(", ")+phistr+phi+degstr;
    renderText(10, height()-10, dirn);

    //what is the mouse pointing at
    //QPoint pos=mapFromGlobal(QCursor::pos());
    //    std::cout << pos().x() << ", " << pos().y() << std::endl;

    renderText(width()-fontMetrics().width(mouseInfo_)-10, height()-10, mouseInfo_);

    //lights back on
    glEnable(GL_LIGHTING);
    //done
    glFlush();

    if (capture_)
      {
	lock();
	lastImage_=grabFrameBuffer();
	unlock();
      }
}

//construct cylinder out of rectangles
void Viewer3D::
glCylinder(bool wire)
{
  const int steps=20;
  double c[steps+1];
  double s[steps+1];
  glBegin(wire?GL_LINES:GL_QUAD_STRIP);
  for (int i=0; i<=steps; i++)
    {
      double angle=(2*M_PI*i)/steps;
      c[i]=cos(angle);
      s[i]=sin(angle);
      glVertex3f(-0.5, c[i]/2., s[i]/2.);
      glVertex3f(0.5, c[i]/2., s[i]/2.);
    }
  glEnd();
  glBegin(wire?GL_LINES:GL_TRIANGLES);
  for (int i=0; i<=steps; i++)
    {
      glVertex3f(-0.5, 0., 0.);
      glVertex3f(-0.5, (i?c[i-1]:c[steps])/2., (i?s[i-1]:s[steps])/2.);
      glVertex3f(-0.5, c[i]/2., s[i]/2.);
    }
  glEnd();
  glBegin(wire?GL_LINES:GL_TRIANGLES);
  for (int i=0; i<=steps; i++)
    {
      glVertex3f(0.5, 0., 0.);
      glVertex3f(0.5, (i?c[i-1]:c[steps])/2., (i?s[i-1]:s[steps])/2.);
      glVertex3f(0.5, c[i]/2., s[i]/2.);
    }
  glEnd();
}
//construct sphere out of triangles
void Viewer3D::
glBlob(bool wire)
{
  const int div=10;
  const float incO=2*M_PI/div;
  const float incA=M_PI/div;
  float px, py, pz;

  if (wire)
    glBegin(GL_LINES);
  else
    glBegin(GL_TRIANGLE_STRIP);
  for (int i= 0 ; i< div; i++)
    {
      for (int j = 0; j<=div ; j++) 
	{
	  pz = cos (M_PI-(incA*j))*BLOB_SIZE;
	  py = sin (M_PI-(incA*j))*sin (incO*i)*BLOB_SIZE;
	  px = sin (M_PI-(incA*j))*cos (incO*i)*BLOB_SIZE;
	  glVertex3f (px, py, pz);
	  
	  pz = cos (M_PI-(incA*j))*BLOB_SIZE;
	  py = sin (M_PI-(incA*j))*sin (incO*(i+1))*BLOB_SIZE;
	  px = sin (M_PI-(incA*j))*cos (incO*(i+1))*BLOB_SIZE;
	  glVertex3f (px, py, pz);
	}
    }
  glEnd();

//   //number of traingles to use
//    const int steps=20;
//    //pre-calculate angles to save time later
//    static float cosines[steps+1][steps/2][3];
//    static float sines[steps+1][steps/2][3];
//    static bool trig=false;
//    if (!trig)
//      {
//        trig=true;
//        for (unsigned short j=0; j<steps/2; j++)
// 	 {
// 	   float theta1=j*2*M_PI/steps-M_PI/2;
// 	   float theta2=(j+1)*2*M_PI/steps-M_PI/2;
// 	   for (unsigned short i=0; i<=steps; i++)
// 	     {
// 	       float theta3=i*2*M_PI/steps;
// 	       cosines[i][j][0]=cos(theta1);
// 	       cosines[i][j][1]=cos(theta2);
// 	       cosines[i][j][2]=cos(theta3);
// 	       sines[i][j][0]=sin(theta1);
// 	       sines[i][j][1]=sin(theta2);
// 	       sines[i][j][2]=sin(theta3);
// 	     }
// 	 }
//      }
//    //build from triangles
//    glBegin( GL_TRIANGLE_STRIP );
//    //honestly i copied this from the net.  best not to look at it too hard
//    for (unsigned short j=0; j<steps/2; j++)
//      {
//        for (unsigned short i=0; i<=steps; i++)
// 	 {
// 	   float x=cosines[i][j][1]*cosines[i][j][2];
// 	   float y=sines[i][j][1];
// 	   float z=cosines[i][j][1]*sines[i][j][2];
// 	   glNormal3f(x, y, z);
// 	   glTexCoord2f(i*1./steps, 2*(j+1)*1./steps);
// 	   glVertex3f(BLOB_SIZE*x, BLOB_SIZE*y, BLOB_SIZE*z);
// 	   x=cosines[i][j][0]*cosines[i][j][2];
// 	   y=sines[i][j][0];
// 	   z=cosines[i][j][0]*sines[i][j][2];
// 	   glNormal3f(x, y, z);
// 	   glTexCoord2f(i*1./steps, 2*(j)*1./steps);
// 	   glVertex3f(BLOB_SIZE*x, BLOB_SIZE*y, BLOB_SIZE*z);
// 	 }
//      }
//    //done
//    glEnd();
}


//a cross in the centre of the view
void Viewer3D::
glCrossHair(void)
{
  //just a couple of lines
  const float size=10;
  glBegin(GL_LINE_STRIP);
  glVertex2d(0, -size);
  glVertex2d(0, size);
  glVertex2d(0, 0);
  glVertex2d(-size, 0);
  glVertex2d(size, 0);
  glEnd();
}
//perform necessary gl magic to create shape
void Viewer3D::
renderShape(glShape& s)
{  
  //cleanup first
  if (s.glFull)
    glDeleteLists(s.glFull, 1);
  if (s.glWire)
    glDeleteLists(s.glFull, 1);
  //set colour
  //4'th number is opacity
  GLfloat col[]={s.r, s.g, s.b, 0.0};
  col[3]=opacity_;
  //keep track of where we are in the coordinate index array
  ConnectionIndex i=0;
  //if this shape has faces, not a squiggly line
      //filled
      //create new gl object
      s.glFull=glGenLists(1);
      glNewList(s.glFull, GL_COMPILE);
      //set colour
      glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, col);
      //repeat until end of coordinate index array
      while (i<s.connections.size())
	{
	  //create a polygon with arbitrary number of sides
	  glBegin(GL_POLYGON);
	  //repeat until we see a -1 in the coordinate index, indicating end of polygon, or until we reach the end of the coordinate index
	  while (i<s.connections.size() && s.connections.at(i)!=-1)
	    {
	      //add this point to the polygon
	      //if (s.points[s.index[i]].y<0.)
	      glVertex3d(s.points.at(s.connections.at(i)).x, s.points.at(s.connections.at(i)).y, s.points.at(s.connections.at(i)).z);
	      //move to the next point
	      i++;
	    }
	  //we've hit a -1, move on to the next coordinate index point
	  i++;
	  //end this polygon
	  glEnd();
	}
      //end of coordinate index, therefore end of shape
      //there may be many polygons within, but it sows up as 1 opengl object
      glEndList();
      i=0;

  //wireframe, whether the shape has faces or not, we shall prepare a wireframe version
  //create new opengl ovhect
  s.glWire=glGenLists(1);
  glNewList(s.glWire, GL_COMPILE);
  //same colour, but make fully opaque
  col[3]=1.0;
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, col);
  //repeat process as above
  while (i<s.connections.size())
    {
      //but use lines rather than polygons
      glBegin(GL_LINE_STRIP);
      //      cout << "new line\n";
      while (i<s.connections.size() && s.connections.at(i)!=-1)
	{
	  //geant often produces tracks with steps in them of zero length
	  if (!i || s.connections.at(i-1)==-1 || s.points.at(s.connections.at(i)).x!=s.points.at(s.connections.at(i-1)).x || s.points.at(s.connections.at(i)).y!=s.points.at(s.connections.at(i-1)).y || s.points.at(s.connections.at(i)).z!=s.points.at(s.connections.at(i-1)).z)
	    {
	      glVertex3d(s.points.at(s.connections.at(i)).x, s.points.at(s.connections.at(i)).y, s.points.at(s.connections.at(i)).z);
	      //	      cout << "vertex " << i << " at " << s.points[s.index[i]].x << ", " << s.points[s.index[i]].y << ", " <<  s.points[s.index[i]].z << endl;
	    }
	  i++;
	}
      i++;
      glEnd();
      //      cout << "end of line\n";
    }
  glEndList();

  //  std::cout << "shape " << qPrintable(s.name) << " rendered with ids " << s.glWire << ", " << s.glFull << std::endl;
}


//effect of mouse action by user
Action Viewer3D::
action(void) const
{
  return action_;
}
void Viewer3D::
setAction(Action action)
{
  action_=action;
  //update
  
}
//add a cell hit to internal array
void Viewer3D::
addShape(const glShape& s)
{
  //render shape in gl
  //  cout << "end of list\n";
  //add to appropriate array
  detector_.push_back(s);
}

//go to default viewpoint
void Viewer3D::
reset(bool doinit)
{
  //clear all user actions
  //and go back to initial viewpoint
  origSize_=50;
  origPosn_=DEFORIG;
//   //destroy origin blob  
//   glDeleteLists(glOrigin_, 1);
   moves_.clear();
   //if initial transforms are not disabled
   if (doinit)
     //perform them
     init();
   //no information on bottom right of display
   mouseInfo_="";
}
//initial transforms based on constants in header
void Viewer3D::
init(void)
{
  //if the initial rotation about the x-axis is not zero 
#if INITRX
  //define a matching rotation about the axis
   Move rx;
   rx.isRot=true;
   rx.x=INITRX;
   //add it to the list of transforms
   moves_.push_back(rx);
#endif
   //similarly for an initial rotation about the y-axis
#if INITRY
   Move ry;
   ry.isRot=true;
   ry.y=INITRY;
   moves_.push_back(ry);
#endif
   //and the z-axis
#if INITRZ
   Move rz;
   rz=isRot=true;
   rz.z=INITRZ;
   moves_.push_back(rz);
#endif
   //if the initial x (left-right) transform is non-zero
#if INITX
   //define a matching translation move
   Move tx;
   tx.x=INITX;
   //add it to the list of moves
   moves_.push_back(tx);
#endif
   //similarly for y (up-down)
#if INITY
   Move ty;
   ty.y=INITY;
   moves_.push_back(ty);
#endif
   //and for z (forward-back)
#if INITZ
   Move tz;
   tz.z=INITZ;
   moves_.push_back(tz);
#endif
}
//forget current set of hits and go to default viewpoint
void Viewer3D::
clearShapes(void)
{
  //forget shapes and tracks
  for (int i=0; i<detector_.size(); i++)
    {
      if (detector_.at(i).glFull>=0)
	glDeleteLists(detector_.at(i).glFull, 1);
      if (detector_.at(i).glWire>=0)
	glDeleteLists(detector_.at(i).glWire, 1);
    }
  detector_.clear();
  //go to default viewpoint
  reset();
}

bool Viewer3D::
showFullDet(void) const
{
  return showFullDet_;
}
void Viewer3D::
setShowFullDet(bool s)
{
  showFullDet_=s;
  //re-paint
  //zOrderGL();
}
bool Viewer3D::
showWireFrame(void) const
{
  return showWireFrame_;
}
void Viewer3D::
setShowWireFrame(bool s)
{
  showWireFrame_=s;
  //re-paint
  //zOrderGL();
}
//move to focus on a specific point
void Viewer3D::
seek(float x, float y, float z, float orig)
{
  reset(false);

  origPosn_=orig;
  origSize_=3*orig/250.;

  //move in x
  Move m;
  m.isRot=false;
  m.x=-x;
  m.y=-y;
  m.z=-z;
  moves_.push_back(m);

  //if the initial rotation about the x-axis is not zero 
#if INITRX
  //define a matching rotation about the axis
   Move rx;
   rx.isRot=true;
   rx.x=INITRX;
   //add it to the list of transforms
   moves_.push_back(rx);
#endif
   //similarly for an initial rotation about the y-axis
#if INITRY
   Move ry;
   ry.isRot=true;
   ry.y=INITRY;
   moves_.push_back(ry);
#endif
   //and the z-axis
#if INITRZ
   Move rz;
   rz=isRot=true;
   rz.z=INITRZ;
   moves_.push_back(rz);
#endif


}
//add a move to the list of moves of the geometry
void Viewer3D::
addMove(int deltax, int deltay)
{
  //determine proper effect of mouse move
  Move move;
  memset(&move, 0, sizeof(Move));
  //x/y translation
  if (action_==MOVE)
    {
      //horizontal
      float shift1=origPosn_*1./width()*deltax;
      move.x=shift1;
      //vertical
      float shift2=origPosn_*1./height()*(-deltay);
      move.y=shift2;

    }
  //z translation/rotation about z-axis
  else if (action_==ZOOM)
    {
      //choose translation
      if (abs(deltay)>abs(deltax))
	{
	  //note size of translation
	  float shift=3000./height()*(-deltay);
	  move.z=shift;
	}
      //or rotation, not both
      else
	{
	  //size of rotation
	  move.isRot=true;
	  move.z=180./height()*(-deltax);
	}
    }
  //rotation about x or y axis
  else if (action_==PAN)
    {
      move.isRot=true;
      //choose axis and scale
      if (abs(deltay)>abs(deltax))
	move.x=-180./height()*(-deltay)/3.;
      else
	move.y=180./width()*(deltax)/3.;
    }
  //move rotation vertex
  else if (action_==ORIGIN)
    {
      //move vertex
      if (abs(deltay)>abs(deltax))
	{
	  //move origin and shift self back to compensate
	  float shift=origPosn_/height()*(-deltay);
	  //not having the vertex right on top of, or behind viewer
	  if (origPosn_<10)
	    {
	      shift-=origPosn_-10;
	      origPosn_=10;
	    }
	  //move self
	  move.z=shift;
	  //shift origin
	  origPosn_+=shift;
	}
      else
	{
	  origSize_+=origSize_*(deltax)/10.;
	  if (origSize_<1)
	    origSize_=1;
	}
    }
  //record this viewpoint change
  if (action_!=ORIGIN || move.z!=0)
    moves_.push_back(move);
  //update world
   zOrderGL();
  paintGL();
  updateGL();
  
}

//access shape list
glShapeIndex Viewer3D::
numShapes(void) const
{
  return detector_.size();
}
const glShape& Viewer3D::
shape(glShapeIndex i) const
{
  return detector_.at(i);
}
//set shape visibility
void Viewer3D::
setShapeVis(glShapeIndex i, bool v)
{
  //apply visibility change
  if (i<detector_.size())
    {
      detector_.at(i).show=v;
    }
}

//from outside, force refresh
void Viewer3D::
refresh(void)
{
  zOrderGL();
  paintGL();
  updateGL();
}
//change surface opacity
void Viewer3D::
setOpacity(float o)
{
  if (o<0) o=0;
  if (o>1) o=1;
  //change opacity
  opacity_=o;
  //re-render affected shapes in gl
  for (int i=0; i<detector_.size(); i++)
    renderShape(detector_.at(i));
}
//return shape opacity
float Viewer3D::
opacity(void) const
{
  return opacity_;
}
//set background colour
void Viewer3D::
setBackground(float r, float g, float b)
{
  if (r>1) r=1;
  if (r<0) r=0;
  if (g>1) g=1;
  if (g<0) g=0;
  if (b>1) b=1;
  if (b<0) b=0;
  bgRed_=r;
  bgGreen_=g;
  bgBlue_=b;
  //apply colour as background
  glClearColor(bgRed_, bgGreen_, bgBlue_, 0.0);
}
float Viewer3D::
backgroundRed(void) const
{
  return bgRed_;
}
float Viewer3D::
backgroundGreen(void) const
{
  return bgGreen_;
}
float Viewer3D::
backgroundBlue(void) const
{
  return bgBlue_;
}

void Viewer3D::
lock(void)
{
  lock_.lock();
}
const QImage& Viewer3D::
lastImage(void)
{
  return lastImage_;
}
void Viewer3D::
unlock(void)
{
  lock_.unlock();
}
void Viewer3D::
setCapture(bool capture)
{
  capture_=capture;
  paintGL();
  updateGL();
}
