chiark / gitweb /
another rune
[moebius2.git] / view.c
diff --git a/view.c b/view.c
index 1ce6db66d492867a869ed833b2a9e9c6931067a8..32ae7d7df8a8a39ecd9ad0785f0d2870bed54c89 100644 (file)
--- a/view.c
+++ b/view.c
@@ -5,6 +5,8 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 
+#include <sys/poll.h>
+
 #include "mgraph.h"
 
 #define MAXTRIS (N*2)
@@ -18,16 +20,22 @@ static Vertices conformation;
 static double transform[D3][D3]= {{1,0,0}, {0,1,0}, {0,0,1}};
 static GSL_MATRIX(transform);
 
-const char *input_filename;
+static FILE *input_f;
+static struct stat input_stab;
+static const char *input_filename;
+static int pause_updates;
 
 static void read_input(void) {
-  FILE *f;
   int r;
   
-  f= fopen(input_filename, "rb");  if (!f) diee("input file");
+  if (input_f) fclose(input_f);
+  input_f= fopen(input_filename, "rb");  if (!input_f) diee("input file");
+
+  if (fstat(fileno(input_f), &input_stab)) diee("fstat input file");
+
   errno= 0;
-  r= fread(&conformation,sizeof(conformation),1,f);  if (r!=1) diee("fread");
-  fclose(f);
+  r= fread(&conformation,sizeof(conformation),1,input_f);
+  if (r!=1) diee("fread");
 }
 
 static void transform_coordinates(void) {
@@ -45,6 +53,8 @@ static void transform_coordinates(void) {
   }
 }
 
+static int vertex_in_triangles[N], vertex_in_triangles_checked;
+
 static void addtriangle(int va, int vb, int vc) {
   Triangle *t= &trisbuffer[ntris];
   int k;
@@ -55,30 +65,51 @@ static void addtriangle(int va, int vb, int vc) {
     t->vertex[1][k]= conformation[vb][k];
     t->vertex[2][k]= conformation[vc][k];
   }
+  if (!vertex_in_triangles_checked) {
+    vertex_in_triangles[va]++;
+    vertex_in_triangles[vb]++;
+    vertex_in_triangles[vc]++;
+  }
   displaylist[ntris++]= t;
 }
 
 static void generate_display_list(void) {
-  int vb, ve[3], e;
+  int vb, ve[V6], e;
 
   ntris= 0;
   FOR_VERTEX(vb) {
-    /* We use the two triangles in the parallelogram vb, vb+e0, vb+e1, vb+e2.
+    /* We use the two triangles in the parallelogram vb, vb+e5, vb+e0, vb+e1.
      * We go round each triangle clockwise (although our surface is non-
-     * orientable so it shouldn't matter).
+     * orientable so it shouldn't matter).  Picking the parallelogram
+     * to our right avoids getting it wrong at the join.
      */
-    for (e=0; e<3; e++) ve[e]= EDGE_END2(vb,e);
-    if (ve[1]>=0) {
-      if (ve[0]>=0) addtriangle(vb,ve[0],ve[1]);
-      if (ve[2]>=0) addtriangle(vb,ve[1],ve[2]);
+//if ((vb & YMASK) > Y1) continue;
+//if ((vb & XMASK) > 2) continue; 
+    for (e=0; e<V6; e++) ve[e]= EDGE_END2(vb,e);
+    assert(ve[0]>=0);
+    if (ve[5]>=0) addtriangle(vb,ve[0],ve[5]);
+//continue;
+    if (ve[1]>=0) addtriangle(vb,ve[1],ve[0]);
+  }
+
+  if (!vertex_in_triangles_checked) {
+    int v, expd;
+    FOR_VERTEX(v) {
+      expd= RIM_VERTEX_P(v) ? 3 : 6;
+      if (vertex_in_triangles[v] != expd) {
+       fprintf(stderr,"vertex %02x used for %d triangles, expected %d\n",
+               v, vertex_in_triangles[v], expd);
+//     abort();
+      }
     }
+    vertex_in_triangles_checked= 1;
   }
 }    
 
 static int dl_compare(const void *tav, const void *tbv) {
   int i;
   const Triangle *const *tap= tav, *ta= *tap;
-  const Triangle *const *tbp= tbp, *tb= *tbp;
+  const Triangle *const *tbp= tbv, *tb= *tbp;
   double za=0, zb=0;
   for (i=0; i<3; i++) {
     za += ta->vertex[i][2];
@@ -105,19 +136,21 @@ static Window window;
 static DrawingMode dmred, dmblue, dmwhite;
 static const DrawingMode *dmcurrent;
 static int wwidth=WSZ, wheight=WSZ, wmindim=WSZ, wmaxdim=WSZ;
-static int ncut, currentbuffer, x11depth, x11screen;
+static int ncut, currentbuffer, x11depth, x11screen, wireframe;
 XVisualInfo visinfo;
 
 static double sizeadj_scale= 0.3, eyes_apart, scale_wmindim;
 static double eye_z= -10, eye_x=0;
 static double cut_z= -9;
+static const double eyes_apart_preferred=0.05, eyes_apart_min= -0.02;
+
 
 static void drawtriangle(const Triangle *t) {
   XPoint points[4];
   int i;
 
   for (i=0; i<3; i++) {
-    double *v= t->vertex[i];
+    const double *v= t->vertex[i];
     double x= v[0];
     double y= v[1];
     double z= v[2];
@@ -130,14 +163,16 @@ static void drawtriangle(const Triangle *t) {
   }
   points[3]= points[0];
 
-  XA( XFillPolygon(display,pixmap, dmcurrent->fillgc,
-                  points,3,Convex,CoordModeOrigin) );
+  if (!wireframe)
+    XA( XFillPolygon(display,pixmap, dmcurrent->fillgc,
+                    points,3,Convex,CoordModeOrigin) );
   XA( XDrawLines(display,pixmap, dmcurrent->linegc,
                 points, 4,CoordModeOrigin) );
 }
 
 static const unsigned long core_event_mask=
-  ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ButtonMotionMask;
+  ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ButtonMotionMask|
+  KeyPressMask|SubstructureNotifyMask;
 
 static void mkpixmaps(void) {
   for (currentbuffer=0; currentbuffer<2; currentbuffer++) {
@@ -205,11 +240,13 @@ static void display_conformation(void) {
   XA( XFillRectangle(display,pixmap,dmwhite.fillgc,0,0,wwidth,wheight) );
 
   if (eyes_apart > 0) {
-    const double preferred=0.05, beyond=0.07;
+    const double stationary= 0.07;
 
-    eye_x= eyes_apart < preferred ? eyes_apart :
-           eyes_apart < beyond ? preferred :
-           eyes_apart - (beyond - preferred);
+    eye_x= eyes_apart < eyes_apart_preferred
+              ? eyes_apart :
+           eyes_apart < (eyes_apart_preferred + stationary)
+              ? eyes_apart_preferred
+              : eyes_apart - stationary;
     eye_x /= sizeadj_scale;
     drawtriangles(&dmblue);
     eye_x= -eye_x;
@@ -235,7 +272,7 @@ static void show(void) {
 
 typedef struct {
   const char *name;
-  void (*start)(void);
+  void (*start)(const XButtonEvent *e);
   void (*delta)(double dx, double dy);
   void (*conclude)(void);
   void (*abandon)(void);
@@ -247,10 +284,11 @@ typedef struct {
     drag_##x##_conclude, drag_##x##_abandon    \
   }
 
-#define DRAG_SAVING(x, thing)                          \
+#define DRAG_SAVING(x, thing, hook)                    \
   static typeof(thing) original_##thing;               \
-  static void drag_##x##_start(void) {                 \
+  static void drag_##x##_start(const XButtonEvent *e) {        \
     memcpy(&original_##thing, &thing, sizeof(thing));  \
+    hook;                                              \
   }                                                    \
   static void drag_##x##_conclude(void) { }            \
   static void drag_##x##_abandon(void) {               \
@@ -259,7 +297,7 @@ typedef struct {
   }                                                    \
   DRAG(x)
 
-static void drag_none_start(void) { }
+static void drag_none_start(const XButtonEvent *e) { }
 static void drag_none_delta(double dx, double dy) { }
 static void drag_none_conclude(void) { }
 static void drag_none_abandon(void) { }
@@ -282,6 +320,25 @@ static void pmatrix(const char *n, double m[D3][D3]) {
 }
 #define PMATRIX(x) pmatrix(#x,x);
 
+static double drag_transform_conv_x_z= 0;
+static double drag_transform_conv_y_z= 0;
+
+static void drag_transform_prep(const XButtonEvent *e) {
+  static const double factor= 2.5;
+  drag_transform_conv_x_z= MAX( MIN(e->y * factor / wheight - (factor/2),
+                                   1.0), -1.0);
+  drag_transform_conv_y_z= MAX( MIN(e->x * factor / wwidth  - (factor/2),
+                                   1.0), -1.0);
+  printf("drag_transform_conv_{x,y}_z = %g,%g\n",
+        drag_transform_conv_x_z, drag_transform_conv_y_z);
+}
+
+static void make_z_rotation(double rotz[D3][D3], double cz, double sz) {
+  rotz[0][0]=  cz;    rotz[0][1]=  sz;     rotz[0][2]=  0;
+  rotz[1][0]= -sz;    rotz[1][1]=  cz;     rotz[1][2]=  0;
+  rotz[2][0]=   0;    rotz[2][1]=   0;     rotz[2][2]=  1;
+}
+
 static void drag_rotate_delta(double dx, double dy) {
   /* We multiple our transformation matrix by a matrix:
    *
@@ -296,31 +353,56 @@ static void drag_rotate_delta(double dx, double dy) {
    * we make cy and sy be cos() and sin(hypot(x,y)) and use
    * with cr,sr as cos() and sin(atan2(y,y)):
    *
-   * Ie we would do  T' = R^T X R T   where
-   *             or  T' =    C    T   where  C = R^T X R  and
+   * Ie we would do  T' = R^T X R T   where
+   *             or  T' =    C    T   where  C = R^T X R  and
    *
    *  adjustment R = [  cr  sr  0 ]
    *                 [ -sr  cr  0 ]
-   *                 [  0    0  1 ]
+   *                 [  0    0  1 ]    or make_z_rotation(cr,sr)
+   *
+   *  rotation Z   = [  cz  sz  0 ]
+   *                 [ -sz  cz  0 ]
+   *                 [  0    0  1 ]    or make_z_rotation(cz,sz)
    */
 
-  double rotx[D3][D3], adjr[D3][D3];
+  double rotx[D3][D3], adjr[D3][D3], rotz[D3][D3];
   GSL_MATRIX(rotx);
   GSL_MATRIX(adjr);
+  GSL_MATRIX(rotz);
 
   static double temp[D3][D3], change[D3][D3];
   static GSL_MATRIX(temp);
   static GSL_MATRIX(change);
 
+  printf("\nTRANSFORM %g, %g\n", dx,dy);
+
+  double dz= -drag_transform_conv_x_z * dx +
+              drag_transform_conv_y_z * dy;
+         
+  dx *= (1 - fabs(drag_transform_conv_x_z));
+  dy *= (1 - fabs(drag_transform_conv_y_z));
+
   double d= hypot(dx,dy);
-  if (d < 1e-6) return;
+
+  printf(" dx,dy,dz = %g, %g, %g    d = %g\n", dx,dy,dz, d);
+
+  if (hypot(d,dz) < 1e-6) return;
+
+  printf(" no xy rotation\n");
 
   double ang= d*2.0;
   
   double cy= cos(ang);
   double sy= sin(ang);
-  double cr= -dy / d;
-  double sr=  dx / d;
+
+  double cr, sr;
+  if (d > 1e-6) {
+    cr= -dy / d;
+    sr=  dx / d;
+  } else {
+    cr= 1;
+    sr= 0;
+  }
   printf("\n d=%g cy,sy=%g,%g cr,sr=%g,%g\n\n", d,cy,sy,cr,sr);
   
   rotx[0][0]=   1;    rotx[0][1]=   0;     rotx[0][2]=  0;
@@ -328,26 +410,33 @@ static void drag_rotate_delta(double dx, double dy) {
   rotx[2][0]=   0;    rotx[2][1]= -sy;     rotx[2][2]= cy;
   PMATRIX(rotx);
 
-  adjr[0][0]=  cr;    adjr[0][1]=  sr;     adjr[0][2]=  0;
-  adjr[1][0]= -sr;    adjr[1][1]=  cr;     adjr[1][2]=  0;
-  adjr[2][0]=   0;    adjr[2][1]=   0;     adjr[2][2]=  1;
+  make_z_rotation(adjr,cr,sr);
   PMATRIX(adjr);
 
   GA( gsl_blas_dgemm(CblasNoTrans,CblasNoTrans, 1.0,
                     &rotx_gsl,&adjr_gsl,
                     0.0, &temp_gsl) );
-  PMATRIX(temp);
+  pmatrix("X R", temp);
   
   GA( gsl_blas_dgemm(CblasTrans,CblasNoTrans, 1.0,
                     &adjr_gsl,&temp_gsl,
                     0.0, &change_gsl) );
   PMATRIX(change);
 
+  double angz= dz*2.0;
+  make_z_rotation(rotz,cos(angz),sin(angz));
+  PMATRIX(rotz);
+
+  GA( gsl_blas_dgemm(CblasTrans,CblasNoTrans, 1.0,
+                    &rotz_gsl,&change_gsl,
+                    0.0, &temp_gsl) );
+  pmatrix("Z C", temp);
+
   static double skew[D3][D3];
   static GSL_MATRIX(skew);
   
   GA( gsl_blas_dgemm(CblasNoTrans,CblasNoTrans, 1.0,
-                    &change_gsl,&transform_gsl,
+                    &temp_gsl, &transform_gsl,
                     0.0, &skew_gsl) );
   PMATRIX(skew);
 
@@ -389,22 +478,21 @@ static void drag_rotate_delta(double dx, double dy) {
   printf("drag_rotate_delta...\n");
   show();
 }
-DRAG_SAVING(rotate, transform);
+DRAG_SAVING(rotate, transform, drag_transform_prep(e));
 
 static void drag_sizeadj_delta(double dx, double dy) {
   sizeadj_scale *= pow(3.0, -dy);
   show();
 }
-DRAG_SAVING(sizeadj, sizeadj_scale);
+DRAG_SAVING(sizeadj, sizeadj_scale);
 
 static void drag_3d_delta(double dx, double dy) {
-  const double min_eyes_apart= -0.02;
   eyes_apart += dx * 0.1;
-  if (eyes_apart < min_eyes_apart) eyes_apart= min_eyes_apart;
+  if (eyes_apart < eyes_apart_min) eyes_apart= eyes_apart_min;
   printf("sizeadj eyes_apart %g\n", eyes_apart);
   show();
 }
-DRAG_SAVING(3d, eyes_apart);
+DRAG_SAVING(3d, eyes_apart);
 
 static const Drag *drag= &drag_none;
 
@@ -437,7 +525,7 @@ static void event_button(XButtonEvent *e) {
           drag->name, (unsigned long)e->button, e->x, e->y);
     drag_last_x= e->x;
     drag_last_y= e->y;
-    drag->start();
+    drag->start(e);
   }
   if (e->type == ButtonRelease) {
     printf("drag=%s release %d,%d\n", drag->name, e->x, e->y);
@@ -452,6 +540,85 @@ static void event_motion(int x, int y) {
   drag_position(x,y);
 }
 
+static void transform_preset_record(const char *fn, const char *fn_new) {
+  FILE *f;
+  f= fopen(fn_new,"wb");
+  if (!f) diee("open new transform");
+  if (fwrite(transform,sizeof(transform),1,f) != 1) diee("write transform");
+  if (fclose(f)) diee("fclose new transform");
+  if (rename(fn_new,fn)) diee("install transform");
+}
+
+static void transform_preset_playback(const char *fn) {
+  FILE *f;
+  f= fopen(fn,"rb");
+  if (!f && errno==ENOENT) {
+    fprintf(stderr,"no preset %s\n",fn);
+    XBell(display,100);
+    return;
+  }
+  errno= 0;
+  if (fread(transform,sizeof(transform),1,f) != 1) {
+    perror("read preset!");
+    XBell(display,100);
+    return;
+  }
+  fclose(f);
+  show();
+}
+
+static void event_key(XKeyEvent *e) {
+  KeySym ks;
+  XKeyEvent e_nomod;
+  char buf[10], buf_nomod[10];
+  int r, r_nomod;
+
+  r= XLookupString(e,buf,sizeof(buf)-1,&ks,0);
+  if (!r) {
+    printf("XLookupString keycode=%u state=0x%x gave %d\n",
+          e->keycode, e->state, r);
+    return;
+  }
+
+  if (!strcmp(buf,"q"))
+    exit(0);
+  else if (!strcmp(buf,"p"))
+    pause_updates= !pause_updates;
+  else if (!strcmp(buf,"w")) {
+    wireframe= !wireframe;
+    show();
+    return;
+  } else if (!strcmp(buf,"d")) {
+    eyes_apart= eyes_apart>0 ? eyes_apart_min : eyes_apart_preferred;
+    show();
+    return;
+  }
+
+  e_nomod= *e;
+  e_nomod.state= 0;
+  buf_nomod[0]= 0;
+  r_nomod= XLookupString(&e_nomod,buf_nomod,sizeof(buf_nomod)-1,&ks,0);
+  if (r_nomod && !buf_nomod[1] && buf_nomod[0]>='0' && buf_nomod[0]<='9') {
+    char filename[20], filename_new[25];
+    snprintf(filename,sizeof(filename)-1,".view-preset-%s",buf_nomod);
+    snprintf(filename_new,sizeof(filename_new)-1,"%s.new",filename);
+    printf("transform preset %d %s\n", e->state, filename);
+    if (e->state) transform_preset_record(filename,filename_new);
+    else transform_preset_playback(filename);
+    return;
+  }
+
+  printf("unknown key keycode=%d state=0x%x char=%c 0x%02x "
+        "[rnm=%d bnm[0,1]=0x%02x,%02x]\n",
+        e->keycode, e->state, buf[0]>' ' && buf[0]<127 ? buf[0] : '?',
+        buf[0], r_nomod, buf_nomod[0], buf_nomod[1]);
+  printf("%d %d %d %d\n",
+        r_nomod,
+        !buf_nomod[1],
+        buf_nomod[0]>='0',
+        buf_nomod[0]<='9');
+}
+
 static void event_config(XConfigureEvent *e) {
   if (e->width == wwidth && e->height == wheight)
     return;
@@ -468,10 +635,52 @@ static void event_config(XConfigureEvent *e) {
   show();
 }
 
+static void check_input(void) {
+  struct stat newstab;
+  int r;
+
+  r= stat(input_filename, &newstab);
+  if (r<0) diee("could not check input");
+
+#define CI(x) if (newstab.st_##x == input_stab.st_##x) ; else goto changed
+  CI(dev);
+  CI(ino);
+  CI(size);
+  CI(mtime);
+#undef CI
+  return;
+
+ changed:
+  show();
+}
+
+static void topocheck(void) {
+  int v1,e,v2,eprime,v1prime, count;
+  FOR_EDGE(v1,e,v2) {
+    count= 0;
+    FOR_VEDGE(v2,eprime,v1prime)
+      if (v1prime==v1) count++;
+    if (count!=1) {
+      fprintf(stderr,"%02x -%d-> %02x  reverse edge count = %d!\n",
+             v1,e,v2, count);
+      FOR_VEDGE(v2,eprime,v1prime)
+       fprintf(stderr,"%02x -%d-> %02x -> %d -> %02x\n",
+               v1,e,v2,eprime,v1prime);
+      exit(-1);
+    }
+  }
+}
+
 int main(int argc, const char *const *argv) {
+  static const int wantedevents= POLLIN|POLLPRI|POLLERR|POLLHUP;
+
   XEvent event;
-  int k;
+  int k, i, r, *xfds, nxfds, polls_alloc=0;
+  struct pollfd *polls=0;
   int motion_deferred=0, motion_x=-1, motion_y=-1;
+
+  topocheck();
+  if (argc==1) { printf("topology self-consistent, ok\n"); exit(0); }
   
   if (argc != 2 || argv[1][0]=='-') {
     fputs("need filename\n",stderr); exit(8);
@@ -485,21 +694,51 @@ int main(int argc, const char *const *argv) {
 
   XMapWindow(display,window);
   for (;;) {
-    if (motion_deferred) {
-      int r= XCheckMaskEvent(display,~0UL,&event);
-      if (!r) {
+
+    XA( XInternalConnectionNumbers(display, &xfds, &nxfds) );
+    if (polls_alloc <= nxfds) {
+      polls_alloc= nxfds + polls_alloc + 1;
+      polls= realloc(polls, sizeof(*polls) * polls_alloc);
+      if (!polls) diee("realloc for pollfds");
+    }
+    for (i=0; i<nxfds; i++) {
+      polls[i].fd= xfds[i];
+      polls[i].events= wantedevents;
+      polls[i].revents= 0;
+    }
+    XFree(xfds);
+
+    polls[i].fd= ConnectionNumber(display);
+    polls[i].events= wantedevents;
+
+    r= poll(polls, nxfds+1, motion_deferred ? 0 : pause_updates ? -1 : 200);
+    if (r<0) {
+      if (errno==EINTR) continue;
+      diee("poll");
+    }
+
+    for (i=0; i<nxfds; i++)
+      if (polls[i].revents)
+       XProcessInternalConnection(display, polls[i].fd);
+
+    r= XCheckMaskEvent(display,~0UL,&event);
+    if (!r) {
+      if (motion_deferred) {
        event_motion(motion_x, motion_y);
        motion_deferred=0;
-       continue;
       }
-    } else {
-      XNextEvent(display,&event);
+      if (!pause_updates)
+       check_input();
+      continue;
     }
+    
     switch (event.type) {
 
     case ButtonPress:
     case ButtonRelease:     event_button(&event.xbutton);     break;
       
+    case KeyPress:          event_key(&event.xkey);           break;
+
     case ConfigureNotify:   event_config(&event.xconfigure);  break;
 
     case MotionNotify: