www.pudn.com > SurfaceConstructor-1.1.rar > contour.c


/*****************************************************************************
 * contour.c is the source file for the contour tracing portion of an
 * application to construct surfaces from serial secions
 * Oliver Hinds  2004-03-16
 *
 *
 *
 *****************************************************************************/

#include"contour.h"
#include"colors.extern"

/******************************************************
 * globals
 ******************************************************/

/* the image */
static image *imgBuf;
static quadri imgBounds;
static GLuint tex;

/* list of tacks and markers to make contours and marks */
static int curContour = 0;
static list *tacks;
static list *markers;

/* flags for showing tacks on neighboring slices */
static int showTacks = TRUE;
static int showLeftTacks = FALSE;
static int showRightTacks = FALSE;
static int showLabels = TRUE;

/* flags for dragging */
static vertex *dragTack = NULL;
static vertex *rightNeighbor, *leftNeighbor;
static int curNeighborNum = 0;

/* variables related to automatic tack placement while dragging */
long tackPlacePeriod = 400; /* ms */
static int tackPlaceIncrement = 1;

/* mouse modifier state */
static int controlDown = FALSE;
static int shiftDown = FALSE;
static int altDown = FALSE;

/* flags for connecting contour mode */
static int connectingContours = FALSE;

/* draw sizes for the selected and unselected tacks in pixels */
int tackInnerRadius = 4;
int tackWidth = 6;
//static int tackSelectedWidth = 24;

/* sizes for labels */
int labelInnerRadius = 6;
int labelWidth = 7;

//extern int NUMLABELCOLORS;
//extern int FIRST_LABEL_COLOR;

/* marker color, selected and unselected */
static color tackSelectedColor;
static color markerColor;
static color markerSelectedColor;

/* if we should show the line */
static int lineView = TRUE;

/* mouse anchor position for operations by rectangle */
static vector *mouseAnchorPos = NULL;
static color rectOutlineColor;
static float rectFillColor[4] = {0.0f, 0.0f, 0.0f, 0.2f};

/* echo new tack parms */
int echoNewTackCoords = FALSE;
char newTackCoordsFilename[MAX_STR_LEN] = "";
FILE* NEW_TACK_COORDS_FP = NULL;

/* quadric for disks */
GLUquadricObj *quad = NULL;

/* constant for contour smoothing amount */
static double smoothFactor = 0.001;

/******************************************************
 * functions
 ******************************************************/

/**
 * load the images and perform init on them
 */
void contourImgInit() {
  //char imgFilename[MAX_STR_LEN];
  image *img;

  /* validate the curSlice */
  curSlice %= curDataset->numSlices;
  if(curSlice < 0) curSlice += curDataset->numSlices;

  /* load the texture if dynamic textures is on */
  if(dynamicTextures) {
    img = loadImage(curDataset, curSlice);
    curDataset->slices[curSlice] = *img;
    free(img);
  }

  /* get image */
  imgBuf = &curDataset->slices[curSlice];

  tex = curDataset->sliceTextures[curSlice];
}

/**
 * initialize general properties
 */
void contourInit() {
  static int firstRun = TRUE;

  list *cList;
  listNode *ln;
  list *actions;
  contour *cont;

  /* default color stuff */
  markerColor = COLORS[ORANGE];
  markerSelectedColor = COLORS[GREEN];
  tackSelectedColor = COLORS[WHITE];
  rectOutlineColor = COLORS[BLACK];

  /* zero the contour */
  curContour = 0;

  /* initialize things that only need it the first run */
  if(firstRun == TRUE) {
    /* get a quadric object */
    if(quad == NULL) {
      quad = gluNewQuadric();
      gluQuadricDrawStyle(quad, GLU_FILL);
    }

    firstRun = FALSE;
  }

  /* find the placement of the image (square and centered) */
  imgBounds.v1.x = 0;
  imgBounds.v1.y = 0;
  imgBounds.v2.x = imgBuf->width;
  imgBounds.v2.y = 0;
  imgBounds.v3.x = imgBuf->width;
  imgBounds.v3.y = imgBuf->height;
  imgBounds.v4.x = 0;
  imgBounds.v4.y = imgBuf->height;

  curDataset->width = imgBuf->width;
  curDataset->height = imgBuf->height;

/*   img.v1.x = -(imgBuf->width *pixelSize/resizeMultiplier); */
/*   img.v1.y = -(imgBuf->height*pixelSize/resizeMultiplier); */
/*   img.v2.x =  (imgBuf->width *pixelSize/resizeMultiplier); */
/*   img.v2.y = -(imgBuf->height*pixelSize/resizeMultiplier); */
/*   img.v3.x =  (imgBuf->width *pixelSize/resizeMultiplier); */
/*   img.v3.y =  (imgBuf->height*pixelSize/resizeMultiplier); */
/*   img.v4.x = -(imgBuf->width *pixelSize/resizeMultiplier); */
/*   img.v4.y =  (imgBuf->height*pixelSize/resizeMultiplier); */


  /* apply the actions */

  /* get the action list to apply/modify, create if it doesn't exist */
  if(listSize(curDataset->sliceActionLists) > curSlice
     && NULL !=
     (actions=getListNode(curDataset->sliceActionLists,curSlice)->data)) {
    /* apply the actions */
    imgBounds = applyActions(actions,imgBounds);
  }

/*   /\* get the current tack list to apply/modify, create if it doesn't exist *\/ */
/*   while(NULL == (ln = getListNode(curDataset->sliceTackLists,curSlice))) { */
/*     cList = newList(LIST); */
/*     enqueue(curDataset->sliceTackLists, cList); */
/*   } */
/*   cList = (list*) ln->data; */

/*   if(listSize(cList) == 0) { */
/*     tacks = newList(LIST); */
/*     enqueue(cList,tacks); */
/*   } */
/*   else { */
/*     tacks = (list*) getListNode(cList, 0)->data; */
/*   } */

  /* get the current contour to modify, create if it doesn't exist */
  while(NULL == (ln = getListNode(curDataset->sliceContourLists,curSlice))) {
    cList = newList(LIST);
    enqueue(curDataset->sliceContourLists, cList);
  }
  cList = (list*) ln->data;

  /* if the slice has no contours, create one */
  if(listSize(cList) == 0) {
    cont = createContour();
    enqueue(cList, cont);
    tacks = cont->vertices;
  }
  else {
    tacks = ((contour*) getListNode(cList, 0)->data)->vertices;
  }

  /* get the current marker list to apply/modify, create if it doesn't exist */
  while(NULL == (ln = getListNode(curDataset->sliceMarkerLists,curSlice))) {
    markers = newList(LIST);
    enqueue(curDataset->sliceMarkerLists, markers);
  }
  markers = (list*) ln->data;

  strcpy(alertString,"");
}

/**
 * uninitializes the contour drawing and imaging stuff
 */
void contourUninit() {
  /* unload the texture if dynamic textures are on */
  if(dynamicTextures) {
    unloadTexture(curSlice);
  }
}

/**
 * cleans up
 */
void contourDestroy() {
  /* dispose of the quadric object */
  gluDeleteQuadric(quad);
  quad = NULL;

  if(echoNewTackCoords) {
    fclose(NEW_TACK_COORDS_FP);
  }
}

/**
 * check to see if a repaint is needed
 */
int contourRepaintNeeded(long now) {
  return FALSE;
}

/**
 * do contour specific drawing
 */
void contourDraw() {
  listNode *i,*j;
  int ind;
  list *l,*cList, *adjList = NULL;
  contour *cont;
  vertex *t, *prev;
  vector rectCorner1,rectCorner2;
  float maxTexX = (float) imgBuf->width, maxTexY = (float) imgBuf->height;
  GLenum textureMethod = getTextureMethod();
  quadri windowCoords;

  windowCoords = getWindowCoordsQ(imgBounds);  

  if(curDataset->imageSource != VP_NOIMAGES) {
    /* turn on texture mapping */
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glEnable(textureMethod);

    /* bind the texture */
    glBindTexture(textureMethod, tex);

    /* if we have no extensions, calculate the extent of the texture in (0,1) */
    if(textureMethod == GL_TEXTURE_2D) {
      maxTexX = imgBuf->width/(float)(imgBuf->width+imgBuf->padX);
      maxTexY = imgBuf->height/(float)(imgBuf->height+imgBuf->padY);
    }

    /* make a quadrilateral and provide texture coords */
    glBegin(GL_QUADS); {
      glTexCoord2d(0.0,0.0);
      glVertex3d(windowCoords.v1.x, windowCoords.v1.y, 0.0);
      glTexCoord2d(maxTexX,0.0);
      glVertex3d(windowCoords.v2.x, windowCoords.v2.y, 0.0);
      glTexCoord2d(maxTexX,maxTexY);
      glVertex3d(windowCoords.v3.x, windowCoords.v3.y, 0.0);
      glTexCoord2d(0.0,maxTexY);
      glVertex3d(windowCoords.v4.x, windowCoords.v4.y, 0.0);
    } glEnd();
    glDisable(textureMethod);
  }

  /* if we are connecting the contour, store the adjacency list */
  if(connectingContours) {
    adjList = ((contour*) getListNode((list*) getListNode(curDataset->sliceContourLists,curSlice)->data,curContour)->data)->adjacentContours;
  }

  /* draw contours and markers from adjacent slices, if neccessary */

  /* left slice contours and markers */
  if(curSlice > 0 && listSize(curDataset->sliceContourLists) > curSlice-1
     && showLeftTacks) {
    glColor3fv(COLORS[GRAY]);

    /* iterate over contours, drawing each */
    l = (list*)getListNode(curDataset->sliceContourLists,curSlice-1)->data;
    for(i = getListNode(l,0); i; i = (listNode*) i->next) {
      /* get the contour at index */
      cont = (contour*) i->data;
      cList = cont->vertices;

      /* draw the contour lines, if we need to */
      if(lineView) {
	glPointSize(pixelSize*tackWidth);
	glBegin(GL_LINES); {
	  /* iterate over tacks, drawing lines between each */
	  prev = listSize(cList) > 1 ? (vertex*) getListNode(cList,0)->data : NULL;
	  for(j = getListNode(cList,1); prev && j; j = (listNode*) j->next) {
	    t = (vertex*) j->data;
	    drawLine(prev,t);
	    prev = t;
	  }
	  /* if this is a closed contour with more than 2 tacks,
	   * draw a line from last to first */
	  if(j != getListNode(cList,1) && cont->closed == CLOSED) {
	    drawLine(prev,(vertex*) getListNode(cList,0)->data);
	  }
	} glEnd();
      }

      /* draw the tacks in the contour */
      for(j = getListNode(cList,0); j; j = (listNode*) j->next) {
	t = (vertex*) j->data;
	/* draw the tack */
	drawTack(t);
      }
    }

    /* left slice markers, unless connecting contours */
    if(!connectingContours) {
      glColor3fv(COLORS[GRAYED_LIGHT_ORANGE]);
      l = (list*)getListNode(curDataset->sliceMarkerLists,curSlice-1)->data;
      for(i = getListNode(l,0); i; i = (listNode*) i->next) {
	t = (vertex*) i->data;
	/* draw the tack */
	drawTack(t);
      }
    }
  }

  /* right slice contours and markers */
  if(curSlice < curDataset->numSlices-1 &&
     listSize(curDataset->sliceContourLists) > curSlice+1 &&
     (showRightTacks || connectingContours)) {

    /* iterate over contours, drawing each */
    l = (list*)getListNode(curDataset->sliceContourLists,curSlice+1)->data;
    for(i = getListNode(l,0), ind = 0; i; i = (listNode*) i->next, ind++) {
      glColor3fv(COLORS[DARK_GRAY]);

      /* get the contour at index */
      cont = (contour*) i->data;
      cList = cont->vertices;

      /* draw the contour lines, if we need to */
      if(lineView) {
	glPointSize(pixelSize*tackWidth);
	glBegin(GL_LINES); {
	  /* iterate over tacks, drawing lines between each */
	  prev = listSize(cList) > 1 ? (vertex*) getListNode(cList,0)->data : NULL;
	  for(j = getListNode(cList,1); prev && j; j = (listNode*) j->next) {
	    t = (vertex*) j->data;
	    drawLine(prev,t);
	    prev = t;
	  }
	  /* if this is a closed contour with more than 2 tacks,
	   * draw a line from last to first */
	  if(j != getListNode(cList,1) && cont->closed == CLOSED) {
	    drawLine(prev,(vertex*) getListNode(cList,0)->data);
	  }
	} glEnd();
      }

      /* set the color if this is an adjacent contour */
      if(listContains(adjList,cont)) {
	glColor3fv(getTackColor(ind));
      }

      /* draw the tacks in the contour */
      for(j = getListNode(cList,0); j; j = (listNode*) j->next) {
	t = (vertex*) j->data;
	/* draw the tack */
	drawTack(t);
      }
    }

    /* right slice markers */
    if(!connectingContours) {
      glColor3fv(COLORS[GRAYED_DARK_ORANGE]);
      l = (list*)getListNode(curDataset->sliceMarkerLists,curSlice+1)->data;
      for(i = getListNode(l,0); i; i = (listNode*) i->next) {
	t = (vertex*) i->data;
	/* draw the tack */
	drawTack(t);
      }
    }
  }

  /* current slice contours */
  if(showTacks) {
    /* iterate over contours, drawing each */
    l = (list*)getListNode(curDataset->sliceContourLists,curSlice)->data;
    for(i = getListNode(l,0), ind = 0; i; i = (listNode*) i->next, ind++) {
      /* get the contour at index */
      cont = (contour*)i->data;
      cList = cont->vertices;

      /* draw the contour lines, if we need to */
      if(lineView) {
	glPointSize(pixelSize*tackWidth);
	glBegin(GL_LINES); {
	  glColor3fv(curContour == ind ? getLineColor(ind)
		     : getGrayedLineColor(ind));

	  /* iterate over tacks, drawing lines between each */
	  prev = listSize(cList) > 1 ? (vertex*) getListNode(cList,0)->data : NULL;
	  for(j = getListNode(cList,1); prev && j; j = (listNode*) j->next) {
	    t = (vertex*) j->data;
	    drawLine(prev,t);
	    prev = t;
	  }
	  /* if this is a closed contour with more than 2 tacks,
	   * draw a line from last to first */
	  if(j != getListNode(cList,1) && cont->closed == CLOSED) {
	    drawLine(prev,(vertex*) getListNode(cList,0)->data);
	  }
	} glEnd();
      }

      /* draw the tacks in the contour */
      for(j = getListNode(cList,0); j; j = (listNode*) j->next) {
	t = (vertex*) j->data;

	/* draw the tack */
	if(t->selected) {
	  glColor3fv(tackSelectedColor);
	}
	else {
	  glColor3fv(curContour == ind ? getTackColor(ind)
		     : getGrayedTackColor(ind));
	}
	drawTack(t);

	if(showLabels && t->label != -1) {
	  glColor3fv(getLabelColor(t->label));
	  drawLabel(t);	  
	}
      }
    }
  }

  /* draw the labels */
/*   if(showLabels) { */
/*     l = (list*)getListNode(curDataset->sliceContourLists,curSlice)->data; */
/*     for(i = getListNode(curDataset->sliceLabels,0); i; i = (listNode*) i->next) { */
/*       lab = (label*) i->data; */
      
/*       /\* draw the label if it is on this slice *\/ */
/*       if(lab->sliceLink == l) { /\* choose color based on selection state *\/ */
/* 	glColor3fv(getLabelColor(lab->lab)); */
/*       } */
/*     } */
/*   } */

  /* draw the markers */
  for(i = getListNode(markers,0); i; i = (listNode*) i->next) {
    t = (vertex*) i->data;

    /* draw the marker */
    if(t->selected) { /* choose color based on selection state */
      glColor3fv(markerSelectedColor);
    }
    else {
      glColor3fv(markerColor);
    }
    drawTack(t);
  }

  /* draw the mouse rectangle, if we should */
  if(mouseAnchorPos != NULL) {
    rectCorner1 = getWindowCoordsV(*mouseAnchorPos);
    rectCorner2 = getWindowCoordsV(lastPos);

    glColor3fv(rectOutlineColor);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glRectf(rectCorner1.x,rectCorner1.y,rectCorner2.x,rectCorner2.y);

    glColor4fv(rectFillColor); 
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);     
    glRectf(rectCorner1.x,rectCorner1.y,rectCorner2.x,rectCorner2.y);
  }

  /* build the mode string */
  sprintf(modeString,"slice %d ",curSlice);
  strcat(modeString,"trace");
  
  if(connectingContours) {
    strcat(modeString," (connecting contours)");   
  }

  strcat(modeString,":");

}

/**
 * draw a tack
 */
void drawTack(vertex *t) {
  vertex v = getWindowCoordsVert(*t);

  /* draw an annulus */
  glTranslated(v.x, v.y,0);
  gluDisk(quad,tackInnerRadius,tackWidth,20,1);
  glTranslated(-v.x, -v.y,0);

  /* make a tiny point */
  glPointSize(1);
  glBegin(GL_POINTS); {
    glVertex3d(v.x, v.y, 0);
  } glEnd();
}

/**
 * draw a label
 */
void drawLabel(vertex *t) {
  vertex v = getWindowCoordsVert(*t);

  /* draw an annulus */
  glTranslated(v.x, v.y,0);
  gluDisk(quad,labelInnerRadius,labelWidth,20,1);
  glTranslated(-v.x, -v.y,0);
}

/**
 * draw a line between two tacks
 */
void drawLine(vertex *t1, vertex *t2) {
  vertex v1,v2,v3,v4;
  double len;

  v1 = getWindowCoordsVert(*t1);
  v2 = getWindowCoordsVert(*t2);

  /* get the coordinates of the intersection of the circles
     and the line*/
  len = tackWidth/(length(v1,v2));
  v3.x = v1.x + len*(v2.x-v1.x);
  v3.y = v1.y + len*(v2.y-v1.y);
  v4.x = v2.x + len*(v1.x-v2.x);
  v4.y = v2.y + len*(v1.y-v2.y);

  /* draw the line up to the tack circles */
  glVertex3d(v3.x, v3.y, 0);
  glVertex3d(v4.x, v4.y, 0);
}

/** geom funcs **/

/** event handlers **/

/**
 * keyboard handler
 */
void contourAction(int action) {
  contour *cont;
  int ret;

  switch(action) {
  case 'b': /* move back one contour */
    decreaseContour();
    break;
  case 'c': /* toggle the closure state of the contour */
    toggleClosureState(curContour);
    redisplay();
    break;
  case 'e': /* toggle left tack display */
    showLeftTacks = !showLeftTacks;
    redisplay();
    break;
  case 'f': /* decrease tacking speed */
    tackPlacePeriod += tackPlaceIncrement;
    break;
  case 'g': /* increase tacking speed */
    tackPlacePeriod -= tackPlaceIncrement;
    break;
  case 'k': /* break contour */
    breakContour();
    break;
  case 'J': /* join contours */
    joinTwoContours();
    break;
  case 'l': /* attach a label */
    setLabelSelected(1);
    redisplay();
    break;
  case 'L': /* detach label */
    setLabelSelected(-1);
    redisplay();
    break;
  case 'E': /* line view toggle */
    lineView = !lineView;
    redisplay();
    break;
  case 'n': /* create a new contour */
  case 'N':
    cont = createContour();
    tacks = cont->vertices;
    enqueue((list*)getListNode(curDataset->sliceContourLists,curSlice)->data,
	    cont);
    curContour = listSize((list*)getListNode(curDataset->sliceContourLists,curSlice)->data) - 1;
    redisplay();
    break;
  case 'p': /* print the tack list */
    fprintf(stdout,"---------------------------------------\n");
    dumpTackList(tacks);
    fprintf(stdout,"---------------------------------------\n");
    break;
  case 't': /* toggle contour connection mode */
    connectingContours = !connectingContours;
    redisplay();
    break;
  case 'j': /* fill selected */
    ret = selectVerticesBetween(tacks);
    if(DEBUG && ret == SR_FAILURE) {    
      fprintf(stderr,"selection fill failed\n");
    }
    redisplay();
    break;
  case 'u': /* unselect */
    unselectAllTacksContour(tacks);
    redisplay();
    break;
  case 'U':
    unselectAllTacksSlice((list*)getListNode(curDataset->sliceContourLists,curSlice)->data);
    redisplay();
    break;
  case 'a':
    selectAllTacksSlice((list*)getListNode(curDataset->sliceContourLists,curSlice)->data);
    redisplay();
    break;
  case 'v': /* move up one contour */
    increaseContour();
    break;
  case 'w': /* toggle current slice tack display */
    showTacks = !showTacks;
    redisplay();
    break;
  case 'W': /* toggle current slice label display */
    showLabels = !showLabels;
    redisplay();
    break;
  case 'y': /* smooth the current contour */
    smoothContour(curSlice,curContour,smoothFactor);
    redisplay();
    break;
  case 'Y': /* smooth all contours */
    smoothAllContours(smoothFactor);
    redisplay();
    break;
  case 'P': /* preprocess contours */
    resample = TRUE;
    preprocessSliceContours(curDataset->sliceContourLists);
    redisplay();
    break;
  case 'r': /* toggle right tack display */
    showRightTacks = !showRightTacks;
    redisplay();
    break;
  case 'x': /* reverse the direction of the current contour */
    reverseList(tacks);
    redisplay();
    break;
  default:
    break;
  }
}

/**
 * creates the menu for the contour
 */
void createContourMenu() {
  glutAddMenuEntry("-- Contour Specific Actions --",0);
  glutAddMenuEntry("'n' create new contour",'n');
  glutAddMenuEntry("'E' toggle line view",'L');
  glutAddMenuEntry("'c' toggle contour closure state",'c');
  glutAddMenuEntry("'k' break contour",'k');
  glutAddMenuEntry("'J' join two contours with selected vertices",'J');
  glutAddMenuEntry("'v' increase current contour",'v');
  glutAddMenuEntry("'b' decrease current contour",'b');
  glutAddMenuEntry("'g' increase tacking speed",'g');
  glutAddMenuEntry("'f' decrease tacking speed",'f');
  glutAddMenuEntry("'w' toggle contour visibility",'w');
  glutAddMenuEntry("'W' toggle label visibility",'W');
  glutAddMenuEntry("'e' toggle left contour visibility",'e');
  glutAddMenuEntry("'r' toggle right contour visibility",'r');
  glutAddMenuEntry("'x' reverse the direction of the current contour",'x');
  glutAddMenuEntry("'t' toggle contour connection mode",'t');
  glutAddMenuEntry("'l' label selected vertices",'l');
  glutAddMenuEntry("'L' unlabel selected vertices",'L');
  glutAddMenuEntry("'u' unselect current contour tacks",'u');
  glutAddMenuEntry("'j' fill selection of current contour tacks",'j');
  glutAddMenuEntry("'a' select current slice tacks",'a');
  glutAddMenuEntry("'U' unselect current slice tacks",'U');
}

/**
 * keyboard handler
 */
void contourKeyboard(unsigned char key, int x, int y) {
  contourAction(key);
}

/**
 * mouse handler determines what kind of actions are being performed
 */
void contourMouse(int button, int state, vector mousePos) {
  int r, l, selectedCount;
  vertex *t, *lastT = NULL;
  list *tackClone, *markerClone;
  int mod;
  
  /* assign a modifier so we can see them in other funcitons */
  if(state == GLUT_DOWN) {
    mod = glutGetModifiers();

    switch(glutGetModifiers()) {
    case GLUT_ACTIVE_CTRL:
      controlDown = TRUE;
      break;
    case GLUT_ACTIVE_SHIFT:
      shiftDown = TRUE;
      break;
    case GLUT_ACTIVE_ALT:
    case GLUT_ACTIVE_SHIFT | GLUT_ACTIVE_CTRL:
      altDown = TRUE;
      break;
    }

  }
  else {
    controlDown = shiftDown = altDown = FALSE;
  }

  /* choose action based on button */
  switch(button) {
  case GLUT_LEFT_BUTTON:
    /* left button: lay or delete a tack or select one to move */
    switch(state) {
    case GLUT_DOWN:
      /* mouse down means different things based on the selected
	 state, whether control/shift is down, whether certains modes
	 are enabled etc..., so lotsa conditionals based on these
	 things */

      /* test to see if we specifying contour connectivity across slices*/
      if(connectingContours) {
	connectContours(curSlice,curContour,&mousePos);
	break;
      }
      else if(controlDown) { /* store the location for possible rectangle select */
	mouseAnchorPos = (vector*) malloc(sizeof(vector));
	*mouseAnchorPos = mousePos;
	break;
      }

      /* test for clicking on an existing tack */
      if(NULL != (t = getTackByLoc(tacks,mousePos.x,mousePos.y)) || /* on tack, move or delete */
	 NULL != (t = getTackByLoc(markers,mousePos.x,mousePos.y))) {
	if(controlDown) { /* if ctrl down, handle neighbors */
	  /* assign the current neighbor */
	  if(curNeighborNum == 0) {
	    if(leftNeighbor) { /* unselect any left neighbor that may exist */
	      //leftNeighbor->selected = FALSE;
	      leftNeighbor = NULL;
	    }

	    /* test for in a tack */
	    if(NULL != (leftNeighbor = getTackByLoc(tacks,mousePos.x,mousePos.y))) {
	      /* check for redundant neighbors */
	      if(leftNeighbor == rightNeighbor) {
		leftNeighbor = NULL;
		rightNeighbor->selected = FALSE;
		rightNeighbor = NULL;
	      }
	      else {
		leftNeighbor->selected = TRUE;
		curNeighborNum = !curNeighborNum;
	      }
	    }
	  }
	  else {
	    /* unselect any right neighbor that may exist */
	    if(rightNeighbor) {
	      //rightNeighbor->selected = FALSE;
	      rightNeighbor = NULL;
	    }

	    /* test for in a tack */
	    if(NULL != (rightNeighbor = getTackByLoc(tacks,mousePos.x,mousePos.y))) {
	      /* check for redundant neighbors */
	      if(leftNeighbor == rightNeighbor) {
		rightNeighbor = NULL;
		leftNeighbor->selected = FALSE;
		leftNeighbor = NULL;
	      }
	      else {
		rightNeighbor->selected = TRUE;
		curNeighborNum = !curNeighborNum;
	      }
	    }
	  }
	}
	else { /* no control down, move tack */
	  dragTack = t;
	  dragTack->selected = TRUE;
	}
      }
      else if(!altDown)  { /* add a new tack */
	if(shiftDown && /* add a new marker */
	   NULL != (dragTack = addTack(markers,mousePos.x,mousePos.y,curSlice))) {
	  dragTack->selected = TRUE;
	}
	else if(rightNeighbor && leftNeighbor) { /* insert between tacks */
	  /* get indices of neighbors */
	  r = getIndexOfTack(tacks, rightNeighbor);
	  l = getIndexOfTack(tacks, leftNeighbor);

	  /* insert tack */
	  if(NULL != (dragTack = insertTack(tacks,mousePos.x,mousePos.y, curSlice, min(r,l)))) {
	    if(abs(r-l) > 1) {
	      fprintf(stderr, "warning: the right and left neighbors specified aren't adjacent\ninserting after lower index.\n");
	      break;
	    }
	    dragTack->selected = TRUE;

	    /* unset the right and left neighbors */
	    rightNeighbor->selected = FALSE;
	    rightNeighbor = NULL;
	    leftNeighbor->selected = FALSE;
	    leftNeighbor = NULL;
	    curNeighborNum = 0;
	  }
	} /* insert a new tack at the end */
	else if(NULL != (dragTack = addTack(tacks,mousePos.x,mousePos.y,curSlice))) {
	  if(echoNewTackCoords) {
	    fprintf(NEW_TACK_COORDS_FP,"%0.5f %0.5f %d\n",
		    mousePos.x,mousePos.y,curSlice);
	  }

	  dragTack->selected = TRUE;	  
	}
      }
      break;
    case GLUT_UP:
      /* stop the dragging */
      if(NULL != dragTack) {
	if(dragTack != leftNeighbor && dragTack != rightNeighbor) {
	  dragTack->selected = FALSE;
	}

	dragTack = NULL;
      }
      else { /* select all the tacks in the rectangle */
	if(mouseAnchorPos == NULL) break;

	tackClone = cloneList(tacks);
	markerClone = cloneList(markers);
	selectedCount = 0;

	while(NULL != (t = getTackInRect(tackClone,mouseAnchorPos->x,mouseAnchorPos->y,
					 mousePos.x,mousePos.y)) ||
	      NULL != (t = getTackInRect(markerClone,mouseAnchorPos->x,mouseAnchorPos->y,
					 mousePos.x,mousePos.y))) {
	  /* select  */
	  t->selected = TRUE;
	  lastT = t;
	  selectedCount++;

	  /* delete based on list */
	  if(-1 != (r = getIndexOfTack(tackClone, t))) {
	    removeListNode(tackClone, r);
	  }
	  else if(-1 != (r = getIndexOfTack(markerClone, t))) {
	    removeListNode(markerClone, r);
	  }
	}
	
	/* store the last two selected vertices */
	if(selectedCount == 1) {	  
	  rightNeighbor = leftNeighbor;
	  leftNeighbor = lastT;
	}

	free(mouseAnchorPos);
	freeList(tackClone);
	freeList(markerClone);
	mouseAnchorPos = NULL;
      }
      break;
    }
    break;
  case GLUT_MIDDLE_BUTTON: /* delete */
    if(state == GLUT_DOWN) {
      /* test to see if we are specifying contour connectivity across slices*/
      if(connectingContours) {
	disconnectContours(curSlice,curContour,&mousePos);
	break;
      }
      else { /* store the location for possible rectangle delete */
	mouseAnchorPos = (vector*) malloc(sizeof(vector));
	*mouseAnchorPos = mousePos;
      }
    }
    else if(state == GLUT_UP) {
      if(mouseAnchorPos == NULL) break;

      while(NULL != (t = getTackInRect(tacks,mouseAnchorPos->x,mouseAnchorPos->y,
				       mousePos.x,mousePos.y)) ||
	    NULL != (t = getTackInRect(markers,mouseAnchorPos->x,mouseAnchorPos->y,
				       mousePos.x,mousePos.y))) {
	/* delete based on list */
	if(-1 != (r = getIndexOfTack(tacks, t))) {
	  removeListNode(tacks, r);
	}
	else if(-1 != (r = getIndexOfTack(markers, t))) {
	  removeListNode(markers, r);
	}
      }
	
      free(mouseAnchorPos);
      mouseAnchorPos = NULL;
    }    
    break;
  }

  /* store the last positions for the up */
  if(state == GLUT_DOWN) {
    lastPos = mousePos;
  }

  redisplay();
}

/**
 * mouse motion handler determines the parameters of the action
 */
void contourMouseMotion(vector mousePos) {
  static long nextTack = 0;
  int needRedraw = FALSE;

  /* we need to redraw if the rectangle is being drawn */
  if(mouseAnchorPos != NULL) {
    needRedraw = TRUE;
  }

  /* check if the mouse dragging should place tacks by timing */
  if(altDown
     && glutGet(GLUT_ELAPSED_TIME) >= nextTack
     && dragTack == getTackByLoc(tacks,mousePos.x,mousePos.y)) {
    /* unselect the dragged tack, if there is one */
    if(dragTack != NULL) {
      dragTack->selected = FALSE;
    }

    /* add a new tack */
    if(NULL != (dragTack = addTack(tacks,mousePos.x,mousePos.y,curSlice))) {
      dragTack->selected = TRUE;
    }
    nextTack = glutGet(GLUT_ELAPSED_TIME) + tackPlacePeriod;
  }
  /* update the position of the dragged tack */
  else if(NULL != dragTack) {
    dragTack->x = mousePos.x;
    dragTack->y = mousePos.y;
    needRedraw = TRUE;
  }

  /* repaint if told to the rectangle thing is happening */
  if(needRedraw) {
    redisplay();
  }

  lastPos = mousePos;
}

/** util **/

/**
 * unselect all tacks in a contour
 */
void unselectAllTacksContour(list *tacks) {
  listNode *ln;

  /* validate */
  if(tacks == NULL) return;

  for(ln = getListNode(tacks,0); ln; ln = (listNode*) ln->next) {
    ((vertex*) ln->data)->selected = FALSE;
  }
}
/**
 * select all tacks in a contour
 */
void selectAllTacksContour(list *tacks) {
  listNode *ln;

  /* validate */
  if(tacks == NULL) return;

  for(ln = getListNode(tacks,0); ln; ln = (listNode*) ln->next) {
    ((vertex*) ln->data)->selected = TRUE;
  }
}

/**
 * unselect all tacks in a slice
 */
void unselectAllTacksSlice(list *contours) {
  listNode *ln;

  /* validate */
  if(contours == NULL) return;

  for(ln = getListNode(contours,0); ln; ln = (listNode*) ln->next) {
    unselectAllTacksContour(((contour*) ln->data)->vertices);
  }  
}

/**
 * select all tacks in a slice
 */
void selectAllTacksSlice(list *contours) {
  listNode *ln;

  /* validate */
  if(contours == NULL) return;

  for(ln = getListNode(contours,0); ln; ln = (listNode*) ln->next) {
    selectAllTacksContour(((contour*) ln->data)->vertices);
  }  
}

/**
 * unselect all tacks in the dataset
 */
void unselectAllTacksDataset(dataset *ds) {
  listNode *ln;

  /* validate */
  if(ds == NULL) return;

  for(ln = getListNode(ds->sliceContourLists,0); ln; ln = (listNode*) ln->next) {
    unselectAllTacksSlice((list*) ln->data);
  }  
}

/**
 * select all tacks in the dataset
 */
void selectAllTacksDataset(dataset *ds) {
  listNode *ln;

  /* validate */
  if(ds == NULL) return;

  for(ln = getListNode(ds->sliceContourLists,0); ln; ln = (listNode*) ln->next) {
    selectAllTacksSlice((list*) ln->data);
  }  
}

/**
 * validate contours by removing empty contours and finding intersecting ones
 * THIS IS POSSIBLY BUGGY DUE TO THE SIMULTANEOUS DELETION AND REFERENCE OF 
 * CONTOURS
 */
int validateContours(dataset *ds) {
  int sliceInd, contInd, intersectingFound = 0, localIntersecting;
  list *slice;
  listNode *sliceNode, *contNode;
  contour *c;

  /* iterate over slices, checking each */
  for(sliceNode = getListNode(ds->sliceContourLists,0), sliceInd = 0;sliceNode;
      sliceNode = (listNode*) sliceNode->next, sliceInd++) {
    slice = (list*) sliceNode->data;

    if(sliceInd == curSlice) {
      continue;
    }

    /* iterate over contours, checking each */
    for(contNode = getListNode(slice,0), contInd = 0; contNode;
	contNode = (listNode*) contNode->next, contInd++) {
      
      c = (contour*) contNode->data;

      /* remove if empty */
      if(listSize(c->vertices) < 1) {
	removeContour(ds,slice,c);
      }
    }

    localIntersecting = selectIntersecting(slice);

    intersectingFound = intersectingFound || localIntersecting;

    if(DEBUG && localIntersecting) {
      fprintf(stdout,"found contour intersection on slice %d\n", sliceInd);
    }      
  }

  return intersectingFound == TRUE ? FAILURE : SUCCESS;
}

/** 
 * select the tacks at endpoints of segments that intersect 
 */
int selectIntersecting(list *contours) {
  int foundIntersecting = 0;
  int localIntersecting = 0;
  contour *thisCont, *otherCont;
  listNode *thisNode, *otherNode;

  /* iterate over each segment, testing for intersection with the others */
  for(thisNode = getListNode(contours,0); thisNode; thisNode = (listNode*) thisNode->next) {
    thisCont = (contour*) thisNode->data;

    /* test this contour against all others, up to and including itself */
    for(otherNode = getListNode(contours,0); otherNode; otherNode = (listNode*) otherNode->next) {
      otherCont = (contour*) otherNode->data;
      localIntersecting = selectContourIntersections(thisCont, otherCont);
      foundIntersecting = foundIntersecting || localIntersecting;

      /* move to the next, if we've just checked ourself */
      if(otherNode == thisNode) break;
    }
  }
  
  return foundIntersecting;
}

/**
 * select contour intersection points 
 */
int selectContourIntersections(contour *c1, contour *c2) {
  int foundIntersecting = 0;
  listNode *ln1, *ln2;
  vertex *v11, *v12, *v21, *v22;

  /* test ech segment for intersection */
  for(ln1 = getListNode(c1->vertices,0); ln1; ln1= (listNode*) ln1->next) {
    if(ln1->next == NULL) break;

    /* vertices for this segment */
    v11 = (vertex*) ln1->data;
    v12 = (vertex*) ((listNode*)ln1->next)->data;

    for(ln2 = getListNode(c2->vertices,0); ln2; ln2 = (listNode*) ln2->next) {
      if(ln2->next == NULL) break;

      /* vertices for this segment */
      v21 = (vertex*) ln2->data;
      v22 = (vertex*) ((listNode*)ln2->next)->data;

      /* if they are the same vertex, dont test */
      if(v21 == v11 || v22 == v11 || v22 == v12 || v21 == v12) continue;

      

      /* test the segments */      
      if(segmentsIntersect2D(v11,v12,v21,v22) == TRUE) {
	v11->selected = v12->selected = v21->selected = v22->selected = TRUE;
	foundIntersecting = TRUE;
      }
    }
  }
  
  return foundIntersecting;
}

/**
 * tests for two dimensional segment intersection 
 */
int segmentsIntersect2D(vertex *v11, vertex *v12, vertex *v21, vertex *v22) {
  return segmentsIntersect(v11->x, v11->y, 0.0f,
			   v12->x, v12->y, 0.0f,
			   v21->x, v21->y, 0.0f,
			   v22->x, v22->y, 0.0f); 
}



/**
 * smooth all contours
 */
void smoothAllContours(double factor) {
  int slice, contour, numContours;

  /* iterate over slices and contours, smoothing each */
  for(slice = 0; slice < curDataset->numSlices; slice++) {
    numContours = listSize((list*) getListNode(curDataset->sliceContourLists,
					       slice)->data);

    for(contour = 0; contour < numContours; contour++) {
      smoothContour(slice, contour, factor);
    }
  }
}

/**
 * smooth the contour across slices
 */
void smoothContour(int sliceIndex, int contourIndex, double factor) {
  smoothContourGauss(sliceIndex, contourIndex, factor);
}

/**
 * smooth the contour across slices using a gaussian kernel
 */
void smoothContourGauss(int sliceIndex, int contourIndex, double factor) {
  int tackInd, sliceInd, contInd, contTackInd;
  double newX, newY, weight;
  list *slice = (list*) getListNode(curDataset->sliceContourLists,
				    sliceIndex)->data;
  list *tacks = ((contour*) getListNode(slice,contourIndex)->data)->vertices,
    *contTacks;
  vertex *contT, *t;

  /* smoothing parm */
  const int sliceRange = 2;


  for(tackInd = 0; tackInd < listSize(tacks); tackInd++) {
    /* init */
    newX = newY = 0.0;
    t = (vertex*) getListNode(tacks,tackInd)->data;

    /* iterate over each slice within range, take weighted average
       of tacks in nearby slices */
    for(sliceInd = curSlice-sliceRange; sliceInd < curSlice+sliceRange;
	sliceInd++) {
      /* validate slice range */
      if(sliceInd < 0 ||
	 sliceInd > curDataset->numSlices ||
	 sliceInd == curSlice) {
	continue;
      }

      /* get the slice */
      slice = (list*) getListNode(curDataset->sliceContourLists,
				  sliceInd)->data;

      /* weighted average of tacks in the slice */
      for(contInd = 0;
	  contInd < listSize(slice);
	  contInd++) {
	contTacks = ((contour*)
		     getListNode(slice,contInd)->data)->vertices;
	for(contTackInd = 0; contTackInd < listSize(contTacks);
	    contTackInd++) {
	  contT = (vertex*) getListNode(contTacks,contTackInd)->data;

	  /* get the weight */
	  weight = gaussian(tackDistance(t,contT));

	  /* update the coordinates */
	  newX += weight * (contT->x - t->x);
	  newY += weight * (contT->y - t->y);;
	}
      }
    }

    /* update the tack location */
    t->x += factor*newX;
    t->y += factor*newY;
  }
}

/**
 * remove a contour from a dataset
 */
void removeContour(dataset *ds, list* slice, contour* cont) {
  int sl,adj;
  list *adjacentList;
  listNode *ln, *aln;
  contour *c;

  /* validate */
  if(slice == NULL || cont == NULL) return;

  /* get the indices of the slice and contour */
  sl = findInListI(ds->sliceContourLists,slice);
  if(sl < 0) return;

  /* validate for adjacent slice correction */
  if(sl > 0) {
    /* delete all refences in the adjacent contours of the previous slice */
    for(ln = getListNode((list*) getListNode(ds->sliceContourLists,sl-1)->data,0); ln; ln = (listNode*) ln->next) {
      adjacentList = ((contour*) ln->data)->adjacentContours;
      for(aln = getListNode(adjacentList,0), adj = 0; aln;
	  aln = (listNode*) aln->next, adj++) {
	c = (contour*) aln->data;

	/* test for this contour */
	if(c == cont) {
	  removeListNode(adjacentList,adj);
	}
      }
    }
  }

  /* remove this contour from the slice contours list */
  removeListNode(slice,findInListI(slice,cont));
}

/**
 * break the contour at selected neighbors
 */
void breakContour() {
  int r,l;
  list *slice;
  contour *newCont, *curCont;

  if(!rightNeighbor || !leftNeighbor) {
    strcpy(alertString,"two neighbors must be selected to break the contour");
    return;
  }

  /* get indices of neighbors */
  r = getIndexOfTack(tacks, rightNeighbor);
  l = getIndexOfTack(tacks, leftNeighbor);

  /* break between tacks */
  if(abs(r-l) > 1) {
    strcpy(alertString, "right and left neighbors specified aren't adjacent\n");
    return;
  }

  slice = (list*) getListNode(curDataset->sliceContourLists,curSlice)->data;
  curCont = (contour*) getListNode(slice,curContour)->data;

  /* perform the split */
  newCont = cloneContour(curCont);
  freeList(newCont->vertices);
  newCont->vertices = splitList(curCont->vertices,max(r,l));

  /* add the new contour */
  enqueue(slice,newCont);

  /* unset the right and left neighbors */
  rightNeighbor->selected = FALSE;
  rightNeighbor = NULL;
  leftNeighbor->selected = FALSE;
  leftNeighbor = NULL;
  curNeighborNum = 0;

  redisplay();
}

/**
 * join the first two contours found with selected vertices
 */
void joinTwoContours() {
  list *slice = (list*) getListNode(curDataset->sliceContourLists,curSlice)->data;
  list *prevSlice;
  contour *tmpCont, *newCont = NULL, *otherCont = NULL;
  listNode *ln, *ln2;

  /* find the newCont */
  for(ln = getListNode(slice,0); ln; ln = (listNode*) ln->next) {
    tmpCont = (contour*) ln->data;

    // find a selected vertex
    if(findFirstSelectedVertex(tmpCont) > -1) {
      newCont = tmpCont;
      break;
    }
  }

  if(newCont == NULL) {
    return;
  }

  /* find the otherCont */
  for(ln = getListNode(slice,0); ln; ln = (listNode*) ln->next) {
    tmpCont = (contour*) ln->data;

    if(tmpCont == newCont) continue;

    // find a selected vertex
    if(findFirstSelectedVertex(tmpCont) > -1) {
      otherCont = tmpCont;
      break;
    }
  }

  if(otherCont == NULL) {
    return;
  }

  /* perform the join */
  appendList(newCont->vertices,otherCont->vertices);
  appendList(newCont->adjacentContours,otherCont->adjacentContours);
  appendList(newCont->adjacentBackwardContours,otherCont->adjacentBackwardContours);

  /* update the adjacency list of the last slice */
  prevSlice = (list*) getListNode(curDataset->sliceContourLists,curSlice-1)->data;
  for(ln = getListNode(prevSlice,0); ln; ln = (listNode*) ln->next) {
    tmpCont = (contour*) ln->data;
    for(ln2 = getListNode(tmpCont->adjacentContours,0); ln2; ln2 = (listNode*) ln2->next) {
      if(ln2->data == otherCont) {
	ln2->data = newCont;
      }
    }
  }

  ln = removeListNode(slice,findInListI(slice,otherCont));
  free((contour*) ln->data);
  free(ln);

  unselectAllTacksSlice(slice);
  redisplay();
}

/**
 * toggles the closure state of a contour on the current slice
 */
void toggleClosureState(int contIndex) {
  contour *cont = (contour*) getListNode((list*) getListNode(curDataset->sliceContourLists,curSlice)->data,contIndex)->data;
  cont->closed = !cont->closed;
}

/**
 * increase the current contour
 */
void increaseContour() {
  list *slice = (list*) getListNode(curDataset->sliceContourLists,curSlice)->data;
  curContour += 1;
  curContour %= listSize(slice);
  tacks = ((contour*) getListNode(slice,curContour)->data)->vertices;

  redisplay();
}

/**
 * decrease the current contour
 */
void decreaseContour() {
  list *slice = (list*) getListNode(curDataset->sliceContourLists,curSlice)->data;
  curContour -= 1;
  if(curContour < 0) {
    curContour += listSize(slice);
  }
  tacks = ((contour*) getListNode(slice,curContour)->data)->vertices;
  redisplay();
}

/**
 * attempt to connect this contour to contours on adjacent slices
 * based on a mouse click position
 */
void connectContours(int sliceInd, int contInd, vector *mousePos) {
  contour *curCont;
  contour *testCont = NULL;
  list *nextSlice;
  listNode *i;

  /* validate the slice indices */
  if(sliceInd < 0 || sliceInd >= listSize(curDataset->sliceContourLists)-1) {
    return;
  }

  /* assign the current contour and next slice */
  curCont = (contour*)
    getListNode((list*) getListNode(curDataset->sliceContourLists,
				    sliceInd)->data,contInd)->data;

  nextSlice = (list*) getListNode(curDataset->sliceContourLists,sliceInd+1)->data;

  /* iterate over the contours in the next slice to test for in tacks */
  for(i = getListNode(nextSlice,0); nextSlice && i; i = (listNode*) i->next) {
    testCont = (contour*) i->data;
    /* test the tacks for contatinment */
    if(NULL != getTackByLoc(testCont->vertices,mousePos->x,mousePos->y)) {
      if(!listContains(curCont->adjacentContours,testCont)) {
	enqueue(curCont->adjacentContours,testCont);
	return;
      }
    }
  }
}

/**
 * attempt to disconnect this contour to contours on adjacent slices
 * based on a mouse click position
 */
void disconnectContours(int sliceInd, int contInd, vector *mousePos) {
  contour *curCont = (contour*) getListNode((list*) getListNode(curDataset->sliceContourLists,sliceInd)->data,contInd)->data;
  contour *testCont = NULL;
  list *nextSlice;
  int ind;
  listNode *i;

  /* validate */
  i = getListNode(curDataset->sliceContourLists,sliceInd+1);
  if(i == NULL) return;

  /* get next slice */
  nextSlice = (list*) i->data;

  /* iterate over the contours in the next slice to test for in tacks */
  for(i = getListNode(nextSlice,0); nextSlice && i; i = (listNode*) i->next) {
    testCont = (contour*) i->data;
    /* test the tacks for contatinment */
    if(NULL != getTackByLoc(testCont->vertices,mousePos->x,mousePos->y)) {
      if(FAILURE != (ind = findInListI(curCont->adjacentContours,testCont))) {
	removeListNode(curCont->adjacentContours,ind);
	return;
      }
    }
  }
}

/**
 * attach the passed label to all the selected vertices
 */
void setLabelSelected(int l) {
  dataset *ds = curDataset;
  listNode *ln, *ln2;
  list *slice = (list*) getListNode(ds->sliceContourLists,curSlice)->data;
  contour *cont = NULL;
  vertex *vert = NULL;
  label *lab;

  for(ln = getListNode(slice,0); ln; ln = (listNode*) ln->next) {
    cont = (contour*) ln->data;
    for(ln2 = getListNode(cont->vertices,0); ln2; ln2 = (listNode*) ln2->next) {
      vert = (vertex*) ln2->data;

      /* if the vertex is selected, label it */
      if(vert->selected) {
	lab = (label*) malloc(sizeof(label));

	/* assign the fields of the label */
	lab->slice = curSlice;
	lab->cont = curContour;
	
	lab->lab = l;

	lab->sliceLink = slice;
	lab->contourLink = cont;
	lab->vertexLink = vert;

	vert->label = lab->lab;

	vert->selected = FALSE;
      }
    }
  }

  rightNeighbor = leftNeighbor = NULL;
}

/**
 * find the first selected vertex on the contour, or -1 if none
 */
int findFirstSelectedVertex(contour *cont) {
  int ind = -1;
  listNode *ln;
  
  if(cont->vertices == NULL) return -1;
  for(ln = getListNode(cont->vertices,0), ind = 0; ln; 
      ln = (listNode*) ln->next, ind++) {
    if(((vertex*)ln->data)->selected) {
      return ind;
    }
  }

  if(ln == NULL) {
    ind = -1;
  }
  return ind;
}

/**
 * gets a contour point color based on a contour id
 */
color getTackColor(int contourNum) {
  return COLORS[contourNum%NUMTACKCOLORS];
}

/**
 * gets a contour point color based on a contour id and grayed
 */
color getGrayedTackColor(int contourNum) {
  return COLORS[contourNum%NUMTACKCOLORS+NUMCOLORS];
}

/**
 * gets a line color based on a contour id
 */
color getLineColor(int contourNum) {
  return COLORS[(contourNum+contourNum/NUMTACKCOLORS)%NUMTACKCOLORS];
}

/**
 * gets a line color based on a contour id and grayed
 */
color getGrayedLineColor(int contourNum) {
  return COLORS[(contourNum+contourNum/NUMTACKCOLORS)%NUMTACKCOLORS+NUMCOLORS];
}

/**
 * gets a label color
 */
color getLabelColor(int labelNum) {
  return COLORS[FIRST_LABEL_COLOR+labelNum%NUMLABELCOLORS];
}

/* evalautes a gaussian density with zero mean and unit sd */
double gaussian(double x) {
  double sd = 7;
  return 1/(sd*sqrt(2*PI)) * exp(-x*x/(2*sd*sd));
}