ftp.nice.ch/pub/next/science/mathematics/2DLab.3.1.s.tar.gz#/2DLab/TwoDView.m

This is TwoDView.m in view mode; [Download] [Up]

/* Generated by Interface Builder */

#include <stdio.h>
#include <stdlib.h> /* qsort blah blah */

#import "TwoDView.h"
#import "TwoDTSP.h"
#import <math.h>
#import <appkit/appkit.h>
#import <appkit/Matrix.h>
#import <appkit/Form.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>

#define NI PSnewinstance()

#ifdef DEBUG /* put this in so the console sould not get flooded. */
// #define PRINTLOTS
#endif
#define MESSAGE(x) printf("%s\n",x)
@implementation TwoDView

/* Map the square [-1 .. 1]x[-1 .. 1] to the TwoDView, leaving a margin
 * of 10%, and centering the origin.
 *
 * the View must already be lockFocused.
 */
void setUpScaling(TwoDView *self)
{
  NXRect r;
  [self getBounds:&r];
  PStranslate(r.size.width/2,r.size.height/2);
  PSscale(0.9*0.5*r.size.width,0.9*0.5*r.size.height);
}

/*
 * set the drawing attributes (except for instance mode flag).
 * 
 * The code for setting line width averages the two sides of the
 * bounds rectangle, to avoid really nasty-looking lines when the view's
 * aspect ratio is not close to unity.  There are probably better ways
 * to do this.
 *
 * the View must already be lockFocused.
 */
void setUpDrawing(TwoDView *self)
{
  NXRect r;
  [self getBounds:&r];
  self->width=([self->lineWidthSlider floatValue]*2.0)/
              (r.size.width+r.size.height);
  if (self->width)
    PSsetlinewidth(self->width);
  else {
    if (NXDrawingStatus == NX_PRINTING) PSsetlinewidth(0.001);
    else PSsetlinewidth(0.0);
    };
}

/*
 * initialize instance variables relating to the data structures.
 * This is broken out of
 * the newFrame: method because mouseDown: events and open: will
 * also need to initialize those quantities.
 *
 * the instance variable numPoints contains the old value that
 * we will be replacing.
 *
 * cases: numPoints=npoints : no alloc, but clear flags.
 *        numPoints=0, npoints > 0 : alloc arrays, etc.
 *        numPoints>0, npoints = 0 : clobber arrays, etc.
 *        numPoints>0, npoints > 0 : realloc arrays, etc.
 *     
 * this function does NOT put any data in the array.  The caller must
 * do that.
 *
 * storage is not allocated for the Delaunay, Gabriel and relative neighborhood
 * graphs because they could be completely connected (large!) but usually
 * aren't.  They will be allocated in the appropriate doFooGraph routines
 * (below).
 */
void initGeometryStuff(TwoDView *self,int npoints)
{
#ifdef DEBUG
    MESSAGE("initGeometryStuff");
#endif

  if (self->numPoints == npoints) {
    self->mstExists=self->chExists=self->vorExists=
                    self->ggExists=self->rngExists=self->dtExists=NO;
    return;
    };
  if (!self->numPoints) { /* alloc */
    NX_MALLOC(self->data,float,npoints*2);
    NX_MALLOC(self->mst_edge,int,(npoints-1)*2);
    NX_MALLOC(self->ch_vertex,int,npoints);
    NX_MALLOC(self->tsp_edges,int,npoints*2);
    }
  else {
    if (!npoints) { /* clobber */
      NX_FREE(self->data);
      NX_FREE(self->mst_edge);
      NX_FREE(self->ch_vertex);
      NX_FREE(self->tsp_edges);
      }
    else {
      NX_REALLOC(self->data,float,npoints*2);
      NX_REALLOC(self->mst_edge,int,(npoints-1)*2);
      NX_REALLOC(self->tsp_edges,int,npoints*2);
      };
    };
  self->numPoints=npoints;
  [self->numPointsForm setIntValue:self->numPoints at:0];
  self->mstExists=self->chExists=self->vorExists=
                  self->ggExists=self->rngExists=self->dtExists=NO;
}

/*
 * make some text appear in the `Status' box
 */
#define STATUS(s) [statusText setStringValue:s]

/*
 * set things up.
 */
+ newFrame:(NXRect *)frameRect
{
  self = [[super newFrame:frameRect] allocateGState]; /* lots of focus pocus */
  numPoints=0; /* this will be fixed */
  operation=OP_PRIM_MST; /* agree with .nib */
  autoUpdate=1; /* agree with .nib */
  srandom(time(0));
  [self random:0];
  displayFlag = DISP_PTS_RESULTS;
  return self;
}

/* create a list of 2D points to demonstrate the algms on.
 * if sender is nil (zero), random: was called from initFrame. Otherwise,
 * the Generate button was pressed.
 */

- random:sender
{
  int i,n;

  if (sender && numPointsForm)
    n = [numPointsForm intValueAt:0];
  else
    n = 10; /* must be initializing */

  initGeometryStuff(self,n);  /* numPOints gets set */

  for(i=0;i<numPoints;i++) {
    X(i)=2.0*(random()&65535)/65536.0 - 1.0;
    Y(i)=2.0*(random()&65535)/65536.0 - 1.0;
    };

  if (sender) {
    if (autoUpdate) [self animThenDisp:0];
    else [self disp:0];
    };
  return self;
}

- mouseDown:(NXEvent *)theEvent
{
  NXPoint where = theEvent->location;
  NXRect r;
  float x,y,cx,cy;
  [self convertPoint:&where fromView:nil];
  [self getBounds:&r];
  cx=r.origin.x+0.5*r.size.width;
  cy=r.origin.y+0.5*r.size.height;

  x=(where.x - cx)/(0.5*0.9*r.size.width)+0.001;
  y=(where.y - cy)/(0.5*0.9*r.size.height)+0.001;
  if ((fabs(x) > 1.0)||(fabs(y) > 1.0)) return self;
#ifdef DEBUG
  fprintf(stderr,"x %f y %f\n",x,y);
#endif

  initGeometryStuff(self,numPoints+1); /* numPoints gets reset */
  X(numPoints-1)=x;
  Y(numPoints-1)=y;

  if (autoUpdate) [self animThenDisp:0];
  else [self disp:0];
  return self;
}

- free
{ initGeometryStuff(self,0); [super free]; return self; }
/*--------------------------------------------------------------------
Enable the form cell if it han an optimal value.
Assume that the cell is disabled.
                                                 Bill Roth/1991-Nov-26
--------------------------------------------------------------------*/

- setOperation:(Matrix *)sender
{ 
    operation=[sender selectedRow]; 
    if (operation >= OP_TSP_STUPID_NEIGHBOR) {
	[optimalValueFormCell setEnabled: YES];
	[timeSlider setEnabled: YES];
	[timeField setEnabled: YES];
    }
    else {
	[optimalValueFormCell setEnabled: NO];
	[timeSlider setEnabled: NO];
	[timeField setEnabled: NO];
	[optimizeButton setEnabled: NO];
    }
    return self; 
}

- setAutoUpdate:(Button *)sender
{ autoUpdate = [sender state]; return self; }


/* This method is invoked when the user bangs on the Animate/Display button. */
/*--------------------------------------------------------------------
Sets the display-pts-only flad, the calls [display] to draw points.
Then does the appropriate drawing routine, then when finished, calls 
[disp] which will call display and tell it do display everything.

                                                 Bill Roth/1991-Nov-17
--------------------------------------------------------------------*/

- animThenDisp:sender
{
#ifdef DEBUG
    MESSAGE("animthenDisp");
#endif

  /* erase prior drawing and display points only */
  displayFlag = DISP_PTS_ONLY;
  [self display];

  /* get ready to draw (in instance mode) */
  [self lockFocus];
  setUpScaling(self);
  NXSetColor([self->highColorWell color]);
  setUpDrawing(self);
  PSsetinstance(YES);

  /* do the operation (drawing in the doFoo methods is all instance drawing) */
  switch(operation) {
    case OP_PRIM_MST: [self doPrimMST]; break;
    case OP_KRUSKAL_MST: [self doKruskalMST]; break;
    case OP_JARVIS_HULL: [self doJarvisHull]; break;
    case OP_GRAHAM_HULL: [self doGrahamHull]; break;
    case OP_VORONOI_DIAGRAM: [self doVoronoiDiagram]; break;
    case OP_DELAUNAY_TRIANGULATION: [self doDelaunayTriangulation]; break;
    case OP_GABRIEL_GRAPH: [self doGabrielGraph]; break;
    case OP_RELATIVE_NEIGHBORHOOD_GRAPH: [self doRelativeNeighborhoodGraph];
                                         break;
    case OP_TSP_STUPID_NEIGHBOR:
			[self doSimpleNearNeighbor]; break;
    case OP_TSP_CHEAPEST_INSERTION: [self doCheapestInsertion]; break;
    case OP_TSP_NEAREST_NEIGHBOR: [self doNearestNeighbor]; break;
    case OP_TSP_NEAREST_ADDITION: [self doNearestAddition]; break;
    case OP_TSP_FARTHEST_INSERTION: [self doFarthestInsertion]; break;
    }

  /* clear instance mode drawing  and reset things*/
  PSsetinstance(NO);
  [self unlockFocus];

  /* display results */
  displayFlag = DISP_PTS_RESULTS;
  [self disp:0];
  return self;
}

-disp:sender
{
  [self display];
  return self;
}


/* Kruskal's MST algorithm (non-optimal quadratic version) */

static float *length;

int ycompar(p1,p2)
  int *p1,*p2;
  {
    float l1=length[*p1],l2=length[*p2];
    if (l1<l2) return -1;
    if (l1>l2) return 1;
    return 0;
  }

float cost(float *p1,float *p2) /* squd Eucl distance between 2 2D points */
{
  float x1,x2,y1,y2;
  x1= p1[0]; y1=p1[1];
  x2= p2[0]; y2=p2[1];
  return((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

/*
  XXX
       THIS IMPLEMENTATION IS NOT OPTIMAL
  XXX
*/

- doKruskalMST
{
  int *group,groupindex=1;
  int *edge,*edgptr,*index,idx;
  int i;
  float *lptr;
#ifdef DEBUG
    MESSAGE("doKruskalMST");
#endif

  STATUS("sorting edges by length");
  NX_MALLOC(edge,int,numPoints*(numPoints-1));
  edgptr=edge;
  NX_MALLOC(length,float,numPoints*(numPoints-1)/2);
  lptr=length;
  /* fill in edge endpoint and length arrays */
  for(i=0;i<numPoints-1;i++) {
    int j;
    char buf[80];
    sprintf(buf,"point %d",i);
    STATUS(buf);
    for(j=i+1;j<numPoints;j++) {
      *edgptr++ = i;
      *edgptr++ = j;
      *lptr++ = cost(XY(i),XY(j));
      };
    };
  STATUS("qsorting");
  NX_MALLOC(index,int,numPoints*(numPoints-1)/2);
  for(i=0;i<numPoints*(numPoints-1)/2;i++) index[i]=i;
  /* sort edges */
  qsort(index,numPoints*(numPoints-1)/2,sizeof(int),ycompar);

  NX_MALLOC(group,int,numPoints);
  bzero(group,numPoints*sizeof(int));
  
  /* Algorithm:
     FOR i = 1 to n-1 
       Add (to the tree) the shortest edge that does not form a circuit in
       the tree.
     ROF
  */
  idx=0;
  for(i=0;i<numPoints-1;i++) {
    char flag=FALSE;
    char buf[80];
    sprintf(buf,"adding edge %d",i);
    STATUS(buf);
    while (!flag) { /* look for an edge */
      int j;
      int v1=edge[index[idx]*2],g1=group[v1];
      int v2=edge[index[idx++]*2+1],g2=group[v2];
      NI;
      [self drawEdge:X(v1):Y(v1):X(v2):Y(v2)];
      NXPing();
      if ((!g1&&!g2)||(g1!=g2)) {
        flag=TRUE;
        mst_edge[i*2]=v1;
        mst_edge[i*2+1]=v2;
        if (g1&&!g2) group[v2]=g1;
        else if(g2&&!g1) group[v1]=g2;
        else if (!g1 && !g2) group[v1]=group[v2]=groupindex++;
        else  /* relabel all g2's to g1 */
          for(j=0;j<numPoints;j++) if (group[j]==g2) group[j]=g1;
        };
      };
    };
  NX_FREE(group);
  NX_FREE(index);
  NX_FREE(length);
  NX_FREE(edge);
  mstExists=YES;
  STATUS("idle");
  return self;
}


#define SIGNEDAREA(x1,y1,x2,y2,x3,y3) (x1*y2+x3*y1+x2*y3-x3*y2-x1*y3-x2*y1)

- doJarvisHull
{
/* Jarvis' march for planar convex hull - From Preparata and Shamos */
  float ymax= -3.0e30,ymin=3.0e30,y,best_x,best_y,candidate_x,candidate_y;
  float current_x,current_y,a;
  int i;
  int nh;
  int current_index=0,best_index=0;
  int min_index=0,max_index=0;
  char *used;
  /* 0. set up storage for indices */
#ifdef DEBUG
    MESSAGE("doJarvisHull");
#endif

  NX_MALLOC(used,char,numPoints);
  /* 1. find min & max in y-coordinate */
  STATUS("finding Ymin vertex");
  for(i=0;i<numPoints;i++) {
    y= Y(i);
    if (y<ymin) { ymin=y; min_index=i; };
    if (y>ymax) { ymax=y; max_index=i; };
    };
  nh = 1;

  ch_vertex[0]=min_index; /* first hull vertex */

  current_index=min_index;
  for(i=0;i<numPoints;i++) used[i]=FALSE;
  /* 2. Jarvis' march - min to max */ 
  STATUS("Jarvis' march: min to max");
  while (current_index!=max_index) {
    used[current_index]=TRUE;
    current_x= X(current_index);
    current_y= Y(current_index);
    best_x= current_x;
    best_y= current_y;
    for(i=0;i<numPoints;i++) if (!used[i]) {
      candidate_x= X(i);
      candidate_y= Y(i);
      NI;
      [self drawEdge:X(current_index):Y(current_index):X(i):Y(i)];
      NXPing();
      a=SIGNEDAREA(current_x,current_y,candidate_x,candidate_y, best_x,best_y);
      if (a>=0.0)
        { best_index=i; best_x=candidate_x; best_y=candidate_y;};
      };
    ch_vertex[nh++] = current_index = best_index;
    };
  /* 3. Jarvis' march - max to min */
  used[min_index]=FALSE;
  STATUS("Jarvis' march: max to min");
  while (current_index!=min_index) {
    used[current_index]=TRUE;
    current_x= X(current_index);
    current_y= Y(current_index);
    best_x=current_x;
    best_y=current_y;
    for(i=0;i<numPoints;i++)  if (!used[i]) {
      candidate_x= X(i);
      candidate_y= Y(i);
      NI;
      [self drawEdge:X(current_index):Y(current_index):candidate_x:candidate_y];
      NXPing();
      a=SIGNEDAREA(current_x,current_y,candidate_x,candidate_y, best_x,best_y);
      if (a>=0.0)
        { best_index=i; best_x=candidate_x; best_y=candidate_y;};
      };
    ch_vertex[nh++] = current_index = best_index;
    };
  nch_vertices=nh;
  NX_FREE(used);
  chExists=YES;
  STATUS("idle");
  return self;
}

/* Graham-hull utils */

#define QUAD(x,y) ((x<0) ? ((y<0)?3:2) : ((y<0)?4:1))

/* the following two declarations are nasty.  They are also the
 * easient way to solve a problem using qsort().  In the Jarvis
 * routine, the vertices are sorted by polar angle wrto the centroid
 * as origin.  However, I want to sort an array of indices to the
 * data rather than the data itself (for efficiency, etc.)  Since I need
 * access to the data by name, and the data is an instance variable, I
 * need to pass self to the plain-C routines and get the actual data by
 * following self.
 */
static float mx,my; /* used in xcompar and doJarvisHull */
static TwoDView *myself;

#define XX(i) myself->data[i*2]
#define YY(i) myself->data[i*2+1]

int xcompar(int *pp1,int *pp2) /*used in Graham scan to sort pts by polar angle*/
{
  int p1,p2,q1,q2;
  float x1,x2,y1,y2,a;
  p1= *pp1; p2= *pp2;
  x1= XX(p1)-mx; y1= YY(p1)-my;
  x2= XX(p2)-mx; y2= YY(p2)-my;
  /* cute li'l optimization.  If we can sort by quadrant, do it. */
  q1=QUAD(x1,y1); q2=QUAD(x2,y2);
  if (q1<q2)  return(-1);
  if (q2<q1)  return(1);
  /* if we get here, p1 & p2 are in same quadrant. shucks. */
  a=SIGNEDAREA(0.0,0.0,x2,y2,x1,y1);
  if (a<0.0) return(-1);
  if (a==0.0) return(0);
  return(1);
}

/* be safe. */
#undef XX
#undef YY

- doGrahamHull
{
  /* Graham's scan for planar convex hull. From Preparata and Shamos */
  int i,mindex=0,start,prev,current,next,nnext;
  int t1,t2,t3;
  float a,minx,xx;
  int *pointers;
  int nh;

  NXSetColor([highColorWell color]);

  /* THIS IS GROSS. */
  myself = self;

  NX_MALLOC(pointers,int,numPoints);
  nh=numPoints;
  /* find point in hull interior (centroid will do)*/
  STATUS("finding interior point");
  mx=0.0; my=0.0; minx=999;
  for(i=0;i<numPoints;i++) {
    mx += X(i);
    my += Y(i);
    };
  mx /= numPoints;
  my /= numPoints;

  for(i=0;i<numPoints;i++) {
    xx= X(i);
    if (xx<minx) { minx=xx; mindex=i;};
    };

  STATUS("sorting points by polar angle");
  /* sort points by polar angle and distance */
  for(i=0;i<numPoints;i++)  pointers[i]=i;
  qsort((void *)pointers,(size_t)numPoints,sizeof(int),xcompar);

  /* find min-X vertex (guaranteed to be on CH) */
  for(i=0;i<numPoints;i++) if (pointers[i]==mindex) break;

  start=current=i; /* point i is a hull vertex. */

  /* set up pointers in linked list */
  prev=(current==0)?nh-1:current-1;
  next=(current==nh-1)?0:current+1;
  nnext=(next==nh-1)?0:next+1;
  /* loop over all points */
  STATUS("Performing Graham's scan");
  while(pointers[next]!=pointers[start]) {
    if (nh==3) break; /* triangle is ALWAYS a convex polygon */
    t1= pointers[current]; t2= pointers[next]; t3= pointers[nnext];
    NI;
    [self drawEdge:X(current):Y(current):X(next):Y(next)];
    [self drawEdge:X(current):Y(current):X(nnext):Y(nnext)];
    [self drawEdge:X(nnext):Y(nnext):X(next):Y(next)];
    NXPing();
    a=SIGNEDAREA(X(t1),Y(t1),X(t2),Y(t2),X(t3),Y(t3));
    if (a<0.0) { /* delete next vertex */
      for(i=next+1;i<nh;i++) pointers[i-1]= pointers[i];
      nh--;
      if (start>=next) start--;
      if (current>=next) current--;
      if (current != start) {
        /* back up one vertex in list */
        current --;
        if (current<0) current += nh;
        prev=(current==0)?nh-1:current-1;
        next=(current==nh-1)?0:current+1;
        nnext=(next==nh-1)?0:next+1;
        }
      else { /* do not back up if current vertex is starting vertex. */
        prev=(current==0)?nh-1:current-1;
        next=(current==nh-1)?0:current+1;
        nnext=(next==nh-1)?0:next+1;
        };
      }
    else { /* move forward in linked list */
      prev=current;
      current=next;
      next=(next==nh-1)?0:next+1;
      nnext=(next==nh-1)?0:next+1;
      };
    };
  for(i=0;i<nh;i++) ch_vertex[i]=pointers[i];
  nch_vertices=nh;
  NX_FREE(pointers);
  chExists=YES;
  return self;
}

- doPrimMST
{
  /* Prim's quadratic MST algorithm - from Horowitz & Sahni */
  int *near;
  int i,j=0,k=0,l=0;
  float mincost,x,xmin;

#ifdef DEBUG
    MESSAGE("doPrimMST");
#endif

  NX_MALLOC(near,int,numPoints);
  bzero(near,numPoints*sizeof(int));
  mincost=10000000000.0;
  /* find shortest edge (O(n^2 time)) */
  STATUS("finding shortest edge");
  for(i=0;i<numPoints-1;i++) {
   char buf[80];
   sprintf(buf,"point %d",i);
   STATUS(buf);
   for(j=i+1;j<numPoints;j++) {
     x=cost(XY(i),XY(j));
     NI;
     [self drawEdge:X(i):Y(i):X(j):Y(j)];
     NXPing();
     if (x<mincost) { mincost=x; k=i; l=j; };
     };
   };
  /* (k,l) is the first edge in the MST */
  mst_edge[0]=k; mst_edge[1]=l;
  NI;
  [self drawEdge:X(k):Y(k):X(l):Y(l)];
  /* set up initial nearest-neighbor array for k and l */
  for(i=0;i<numPoints;i++)
    if (cost(XY(i),XY(l))<cost(XY(i),XY(k)))
      near[i]=l;
    else
      near[i]=k;
  near[k]= near[l]= -1;
  /* add the remaining n-2 edges */
  for(i=1;i<numPoints-1;i++) {
    char buf[80];
    sprintf(buf,"adding edge %d",i);
    STATUS(buf);
    xmin=100000000000.0;
    for(k=0;k<numPoints;k++) 
      if (near[k]!= -1) {
        x=cost(XY(k),XY(near[k]));
        if (x<xmin) { xmin=x; j=k;};
        };
    mst_edge[i*2] = j; mst_edge[i*2+1]= near[j];
    NI;
    [self drawEdge:X(j):Y(j):X(near[j]):Y(near[j])];
    mincost += xmin;
    near[j]= -1;
    for(k=0;k<numPoints;k++)
      if (near[k]!= -1)
        if (cost(XY(k),XY(near[k])) > cost(XY(k),XY(j)))
          near[k]=j;
    };
  NX_FREE(near);
  NXPing();
  mstExists=YES;
  STATUS("idle");
  return self;
}

- erase
{
  NXRect r;
  [[self getBounds:&r] lockFocus];
  NXSetColor([bgColorWell color]);
  NXRectFill(&r);
  [self unlockFocus];
  return self;
}

- drawDT  /* focus must already be locked */
{
  int i;
  for(i=0;i<ndt_edge;i++) {
    int i1=dt_edge[i*2],i2=dt_edge[i*2+1];
    char buf[80];
    sprintf(buf,"DT edge %d",i);
    STATUS(buf);
    [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
    };
  return self;
}

- drawGG  /* focus must already be locked */
{
  int i;
  for(i=0;i<ngg_edge;i++) {
    int i1=gg_edge[i*2],i2=gg_edge[i*2+1];
    char buf[80];
    sprintf(buf,"GG edge %d",i);
    STATUS(buf);
    [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
    };
  return self;
}

- drawRNG  /* focus must already be locked */
{
  int i;
  for(i=0;i<nrng_edge;i++) {
    int i1=rng_edge[i*2],i2=rng_edge[i*2+1];
    char buf[80];
    sprintf(buf,"RNG edge %d",i);
    STATUS(buf);
    [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
    };
  return self;
}

/*--------------------------------------------------------------------
This function does the actual drawing for the algorithms. Most of the 
routines merely draw line between sets of vertices.

                                                 Bill Roth/1991-Nov-17
--------------------------------------------------------------------*/
- drawSelf:(NXRect *)rects:(int)rectCount
{
  int i;
#ifdef DEBUG
  fprintf(stderr,"drawSelf (%d points)\n",numPoints);
#endif

  STATUS("redisplaying");
  NXSetColor([bgColorWell color]);
  NXRectFill(&rects[0]); //fill page with current color.

  setUpScaling(self); // set up scaling

  setUpDrawing(self);
  NXSetColor([self->fgColorWell color]);
  for(i=0;i<numPoints;i++) {
#ifdef PRINTLOTS
    fprintf(stderr,"%d: %f %f\n",i,X(i),Y(i));
#endif
    PSnewpath();
    PSarc(X(i),Y(i),width,0.0,360.0);
    PSfill();
    };
  NXPing();

  /* draw selected data structure */
  if (displayFlag != DISP_PTS_ONLY) {
#ifdef DEBUG
    MESSAGE("drawing everything");
#endif

    switch(operation) {
      case OP_PRIM_MST:
      case OP_KRUSKAL_MST: if (mstExists) {
        for(i=0;i<numPoints-1;i++) {
          int i1=mst_edge[i*2], i2=mst_edge[i*2+1];
          PSnewpath();
          PSmoveto(X(i1),Y(i1));
          PSlineto(X(i2),Y(i2));
          PSstroke();
          NXPing();
          };
        break;
        };
      case OP_JARVIS_HULL:
      case OP_GRAHAM_HULL: if (chExists) {
        PSnewpath();
        PSmoveto(X(ch_vertex[0]),Y(ch_vertex[0]));
        for(i=1;i<nch_vertices;i++) PSlineto(X(ch_vertex[i]),Y(ch_vertex[i]));
        PSclosepath();
        PSstroke();
        NXPing();
        break;
        };
      case OP_VORONOI_DIAGRAM: if (vorExists) {
        float *foo;
        NX_MALLOC(foo,float,numPoints*2);
        for(i=0;i<numPoints;i++) {
          int nv,j;
          if ((nv=find_vregion(i,foo)) == -1) {
            NXRunAlertPanel("Voronoi","find_vregion (%d) failed",
                            NULL,NULL,NULL,i);
            break;
            };
          PSnewpath();
          PSmoveto(foo[0],foo[1]);
          for(j=1;j<nv;j++) PSlineto(foo[j*2],foo[j*2+1]);
          PSclosepath();
          PSstroke();
          NXPing();
          };
        NX_FREE(foo);
        break;
        };
      case OP_DELAUNAY_TRIANGULATION: if (vorExists) [self drawDT]; break;
      case OP_GABRIEL_GRAPH: if (ggExists)  [self drawGG]; break;
      case OP_RELATIVE_NEIGHBORHOOD_GRAPH: if (rngExists) [self drawRNG]; break;
      case OP_TSP_STUPID_NEIGHBOR:
      case OP_TSP_NEAREST_NEIGHBOR: 
      case OP_TSP_CHEAPEST_INSERTION:
      case OP_TSP_NEAREST_ADDITION:
      case OP_TSP_FARTHEST_INSERTION:
	 [self drawTSP]; break;
      };
    };
  NXPing();
  STATUS("idle");
  return self;
}

/* draw an edge with given endpoints */
-drawEdge:(float)x1:(float)y1:(float)x2:(float)y2
{ /*assumes focus is locked already and attributes (color, width) already set*/
  PSnewpath();
  PSmoveto(x1,y1);
  PSlineto(x2,y2);
  PSstroke();
  return self;
}

- doVoronoiDiagram
{
  NXRect r;
  float xmin,xmax,ymin,ymax;

  STATUS("Finding Voronoi diagram");
  [self getBounds:&r];
  xmin=-1.01;
  xmax=1.01;
  ymin=-1.01;
  ymax=1.01;

  if (load_vsites(numPoints,data,xmin,ymin,xmax,ymax)) {
    NXRunAlertPanel("Voronoi","load_vsites failed",NULL,NULL,NULL);
    vorExists=NO;
    return self;
    };
  vorExists=YES;
  return self;
}

static void checkedge(TwoDView *s,int i1,int i2)
{
  int i;
  int ii1=MIN(i1,i2),ii2=MAX(i1,i2);
  for(i=0;i<s->ndt_edge;i++)
    if ((s->dt_edge[i*2]==ii1)&&(s->dt_edge[i*2+1]==ii2)) return;
  s->dt_edge[s->ndt_edge*2]=ii1;
  s->dt_edge[s->ndt_edge*2+1]=ii2;
  s->ndt_edge++;
}

- doDelaunayTriangulation
{
  int ndt,i;
  TRI *tris;
  [self doVoronoiDiagram]; /* cheat! */

  STATUS("building Delaunay triangulation data structures");
  /* from list of triangles, obtain minimal list of edged (toss dups) */
  if ((ndt=find_dtriangles(&tris)) == -1) {
    NXRunAlertPanel("Delaunay","find_dtriangles() failed",
                    NULL,NULL,NULL);
    return self;
    };

  NX_MALLOC(dt_edge,int,2*3*ndt);
  ndt_edge=0;

  for(i=0;i<ndt;i++) {
    int p1=tris[i].v1->pid;
    int p2=tris[i].v2->pid;
    int p3=tris[i].v3->pid;
    checkedge(self,p1,p2);
    checkedge(self,p1,p3);
    checkedge(self,p2,p3);
    };
  dtExists=YES;
#ifdef PRINTLOTS
  for(i=0;i<ndt_edge;i++)
    fprintf(stderr,"%d: %d %d\n",i,dt_edge[i*2],dt_edge[i*2+1]);
#endif
  return self;
}


- doGabrielGraph
{
  int i;

  if (!dtExists)[self doDelaunayTriangulation];
  /* we need to see the Delaunay graph while we're animating */
  NXSetColor([fgColorWell color]);
  PSsetinstance(NO);
  [[[self drawDT] window] flushWindow];
  NXSetColor([highColorWell color]);
  PSsetinstance(YES);

  ngg_edge=ndt_edge;
  NX_MALLOC(gg_edge,int,2*ngg_edge);

  bcopy(dt_edge,gg_edge,2*ngg_edge*sizeof(int));


  /*for each DT edge, check circle of influence */
  for(i=0;i<ngg_edge;i++) {
    int i1=gg_edge[i*2], i2=gg_edge[i*2+1];
    int j;
    float x1=X(i1),y1=Y(i1);
    float x2=X(i2),y2=Y(i2);
    float cx=(x1+x2)/2,cy=(y1+y2)/2,r=0.5*sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
    char buf[80];
#ifdef PRINTLOTS
    fprintf(stderr,"edge %d (%d %d)\n",i,i1,i2);
    fprintf(stderr,"(%f %f) -> (%f %f)\n",x1,y2,x2,y2);
    fprintf(stderr,"center %f %f rad %f\n",cx,cy,r);
#endif
    sprintf(buf,"considering Delaunay edge %d",i);
    STATUS(buf);
    NI;
    PSnewpath();
    PSarc(cx,cy,r,0.0,360.0);
    PSstroke();
    NXPing();

    /* check to see if any other vertex in within the circle of influence.
       if so, remove the edge from the GG */
    for(j=0;j<numPoints;j++) {
      if ((j!=i1)&&(j!=i2)) {
        float x=X(j),y=Y(j),d=(x-cx)*(x-cx)+(y-cy)*(y-cy)-r*r;
#ifdef PRINTLOTS
        fprintf(stderr,"checking %d (%f %f) d %g\n",j,x,y,d);
#endif
        if (d<0.0) { /* clobber edge */
          int k;
#ifdef DEBUG
          fprintf(stderr,"die.\n");
#endif
          for(k=i;k<ngg_edge-1;k++) {
            gg_edge[k*2]=gg_edge[(k+1)*2];
            gg_edge[k*2+1]=gg_edge[(k+1)*2+1];
            };
          ngg_edge--;
          /* highlight the dead edge */
          PSsetinstance(NO);
          PSnewpath();
          PSmoveto(x1,y1);
          PSlineto(x2,y2);
          PSstroke();
          PSsetinstance(YES);
          i--;
          break;
          };
        };
      };
    };
  NX_REALLOC(gg_edge,int,2*ngg_edge);
  ggExists=YES;
  return self;
}

- doRelativeNeighborhoodGraph
{
  int i;

  if (!dtExists)[self doDelaunayTriangulation];
  /* we need to see the Delaunay graph while we're animating */
  PSsetinstance(NO);
  NXSetColor([fgColorWell color]);
  [[[self drawDT] window] flushWindow];
  NXSetColor([highColorWell color]);
  PSsetinstance(YES);

  nrng_edge=ndt_edge;
  NX_MALLOC(rng_edge,int,2*nrng_edge);

  bcopy(dt_edge,rng_edge,2*nrng_edge*sizeof(int));


  /*for each DT edge, check lune of influence */
  for(i=0;i<nrng_edge;i++) {
    int i1=rng_edge[i*2], i2=rng_edge[i*2+1];
    int j;
    float x1=X(i1),y1=Y(i1);
    float x2=X(i2),y2=Y(i2);
    float r2=(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
    float r=sqrt(r2);
    char buf[80];
#ifdef PRINTLOTS
    fprintf(stderr,"edge %d (%d %d)\n",i,i1,i2);
    fprintf(stderr,"(%f %f) -> (%f %f)\n",x1,y2,x2,y2);
    fprintf(stderr,"rad^2 %f\n",r2);
#endif
    sprintf(buf,"considering Delaunay edge %d",i);
    STATUS(buf);
    NI;
    PSnewpath();
    PSarc(x1,y1,r,0.0,360.0);
    PSmoveto(x2,y2); /* eliminate annoying connector */
    PSarc(x2,y2,r,0.0,360.0);
    PSstroke();
    NXPing();

    /* check to see if any other vertex in within the lune of influence.
       if so, remove the edge from the RNG */
    for(j=0;j<numPoints;j++) {
      if ((j!=i1)&&(j!=i2)) {
        float x=X(j),y=Y(j);
        float d1=(x-x1)*(x-x1)+(y-y1)*(y-y1)-r*r;
        float d2=(x-x2)*(x-x2)+(y-y2)*(y-y2)-r*r;
#ifdef PRINTLOTS
        fprintf(stderr,"checking %d (%f %f) d1 %g d2 %g\n",j,x,y,d1,d2);
#endif
        if ((d1<0.0)&&(d2<0.0)) { /* clobber edge */
          int k;
#ifdef PRINTLOTS
          fprintf(stderr,"die.\n");
#endif
          for(k=i;k<nrng_edge-1;k++) {
            rng_edge[k*2]=rng_edge[(k+1)*2];
            rng_edge[k*2+1]=rng_edge[(k+1)*2+1];
            };
          nrng_edge--;
          /* highlight the dead edge */
          PSsetinstance(NO);
          PSnewpath();
          PSmoveto(x1,y1);
          PSlineto(x2,y2);
          PSstroke();
          PSsetinstance(YES);
          i--;
          break;
          };
        };
      };
    };
  NX_REALLOC(rng_edge,int,2*nrng_edge);
  rngExists=YES;
  return self;
}

- saveTIFF:sender
{
  NXRect r;
  id bm=[NXBitmapImageRep alloc];
  id sp=[SavePanel new];
  NXStream *stream;
  int fd;

  [self lockFocus];
  [self getBounds:&r];
  [bm initData:NULL fromRect:&r];
  [self unlockFocus];
  
  [sp setRequiredFileType:"tiff"];
  if (![sp runModal]) return self;
  fd=open([sp filename],O_CREAT|O_RDWR,0644);
  /*[sp free]; screaming death! screaming death! don't do this! */
  stream = NXOpenFile(fd,NX_READWRITE);
  if (!stream) {
    NXRunAlertPanel("TIFF","NXOpenFile failed",NULL,NULL,NULL);
    perror("NXOpenFile");
    [bm free];
    close(fd);
    return self;
    };
  STATUS("saving image as TIFF");
  [bm writeTIFF:stream usingCompression:NX_TIFF_COMPRESSION_LZW];
  [bm free];
  NXClose(stream);
  close(fd);
  STATUS("idle");
  return self;
}

- saveData:sender
{
  id sp=[SavePanel new];
  FILE *fp;
  int i;

  [sp setRequiredFileType:"2Ddata"];
  if (![sp runModal]) return self;
  fp=fopen([sp filename],"w");
  /*[sp free];*/
  if (!fp) {
    NXRunAlertPanel("Data","Couldn't open file",NULL,NULL,NULL);
    return self;
    };
  STATUS("saving data");
  fprintf(fp,"%d\n",numPoints);
  for(i=0;i<numPoints;i++) fprintf(fp,"%g %g\n",X(i),Y(i));
  fclose(fp);
  STATUS("idle");
  return self;
}

- openData:sender
{
  id op=[OpenPanel new];
  const char *const types[] = { "2Ddata" , NULL};
  FILE *fp;
  int i;

  [op setRequiredFileType:"2Ddata"];
  [op runModalForDirectory:"." file:"" types:types];
  chdir([op directory]);
  if (![op filenames]) {
    /*[op free];*/
    return self;
    };

  fp=fopen(*[op filenames],"r");
  /*[op free];*/
  if (!fp) {
    NXRunAlertPanel("Open","Couldn't open file",NULL,NULL,NULL);
    return self;
    };

  STATUS("reading data");
  fscanf(fp,"%d ",&numPoints);
  initGeometryStuff(self,numPoints);
  for(i=0;i<numPoints;i++) {
    fscanf(fp,"%f %f ",&X(i),&Y(i));
    };
  fclose(fp);
  [self disp:0];
  STATUS("idle");
  return self;
}

- close:sender
{
  initGeometryStuff(self,0);
  [self erase];
  return self;
}
  
-lineWidthChanged:sender
{
  char buf[80];
  sprintf(buf,"Line width: %6.3f\n",[sender floatValue]);
  [lineWidthText setStringValue:buf];
  /* could redraw self here */
  if (autoUpdate) [self display];
  return self;
}


-setFgColorWell:sender
{
  fgColorWell = sender;
  [sender setColor:NXConvertGrayToColor(0.0)];
  return self;
}

-setBgColorWell:sender
{
  bgColorWell = sender;
  [sender setColor:NXConvertGrayToColor(1.0)];
  return self;
}


-setHighColorWell:sender
{
  highColorWell = sender;
  [sender setColor:NXConvertGrayToColor(0.666)];
  return self;
}

- (BOOL)acceptsFirstResponder { return YES; }
- (BOOL)acceptsFirstMouse { return YES; }

- copy:sender
{
  id pb=[Pasteboard new];
  NXStream *stream;
  char *d;
  int length,maxLength;

  STATUS("copying image to clipboard");
  [pb declareTypes:&NXPostScriptPboard num:1 owner:self];
  stream=NXOpenMemory(NULL,0,NX_WRITEONLY);
  [self copyPSCodeInside:NULL to:stream];
  NXGetMemoryBuffer(stream,&d,&length,&maxLength);
  [pb writeType:NXPostScriptPboard data:d length:length];
  NXCloseMemory(stream,NX_FREEBUFFER);
  [pb free];
  STATUS("idle");
  return self;
}

- saveEPS:sender
{
  NXStream *stream;
  id sp = [SavePanel new];
  int fd;

  [sp setRequiredFileType:"eps"];
  if (![sp runModal]) return self;
  fprintf(stderr,"filename %s\n",[sp filename]);
  fd=open([sp filename],O_CREAT|O_WRONLY,0644);
  /*[sp free];*/
  stream = NXOpenFile(fd,NX_WRITEONLY);
  if (!stream) {
    NXRunAlertPanel("EPS","NXOpenFile failed",NULL,NULL,NULL);
    perror("NXOpenFile");
    return self;
    };
  STATUS("saving image as EPS");
  [self copyPSCodeInside:NULL to:stream];
  NXClose(stream);
  close(fd);
  STATUS("idle");
  return self;
}
/*--------------------------------------------------------------------
Message from App object.

                                                 Bill Roth/1991-Dec-01
--------------------------------------------------------------------*/

- appDidInit: sender;
{
    drawTime = [timeSlider intValue];
    [timeField setIntValue: drawTime at: 0];
    [timeField setEnabled: NO]; // agree with NIB
	[timeSlider setEnabled: NO];// agree with NIB
    return self ;
}
- doOptimize :sender
{
	[self Optimize];
	return self;
}
@end

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