chiark / gitweb /
07d5fa737bc7ba541cafc8b6e7ffc06c12a83894
[moebius2.git] / project.c
1 /*
2  * Displays a conformation
3  */
4
5 #include <X11/Xlib.h>
6 #include <X11/Xutil.h>
7
8 #include "mgraph.h"
9
10 #define MAXTRIS (N*2)
11
12 typedef struct { double vertex[3][D3]; } Triangle;
13
14 static Triangle trisbuffer[MAXTRIS], *displaylist[MAXTRIS];
15 static int ntris;
16 static Vertices conformation;
17
18 static double transform[D3][D3]= {{1,0,0}, {0,1,0}, {0,0,1}};
19 static GSL_MATRIX(transform);
20
21 const char *input_filename;
22
23 static void read_input(void) {
24   FILE *f;
25   int r;
26   
27   f= fopen(input_filename, "rb");  if (!f) diee("input file");
28   errno= 0;
29   r= fread(&conformation,sizeof(conformation),1,f);  if (r!=1) diee("fread");
30   fclose(f);
31 }
32
33 static void transform_coordinates(void) {
34   double result[D3];
35   GSL_VECTOR(result);
36   gsl_vector input_gsl= { D3,1 };
37
38   int v, k;
39   
40   FOR_VERTEX(v) {
41     input_gsl.data= &conformation[v][0];
42     GA( gsl_blas_dgemv(CblasNoTrans, 1.0,&transform_gsl, &input_gsl,
43                        0.0, &result_gsl) );
44     K conformation[v][k]= result[k];
45   }
46 }
47
48 static void addtriangle(int va, int vb, int vc) {
49   Triangle *t= &trisbuffer[ntris];
50   int k;
51   
52   assert(ntris < MAXTRIS);
53   K {
54     t->vertex[0][k]= conformation[va][k];
55     t->vertex[1][k]= conformation[vb][k];
56     t->vertex[2][k]= conformation[vc][k];
57   }
58   displaylist[ntris++]= t;
59 }
60
61 static void generate_display_list(void) {
62   int vb, ve[3], e;
63
64   ntris= 0;
65   FOR_VERTEX(vb) {
66     /* We use the two triangles in the parallelogram vb, vb+e1, vb+e0, vb+e2.
67      * We go round each triangle clockwise (although our surface is non-
68      * orientable so it shouldn't matter).
69      */
70     for (e=0; e<3; e++) ve[e]= EDGE_END2(vb,e);
71     if (ve[0]>=0) {
72       if (ve[1]>=0) addtriangle(vb,ve[0],ve[1]);
73       if (ve[2]>=0) addtriangle(vb,ve[2],ve[0]);
74     }
75   }
76 }    
77
78 static int dl_compare(const void *tav, const void *tbv) {
79   const Triangle *const *tap= tav, *ta= *tap;
80   const Triangle *const *tbp= tbp, *tb= *tbp;
81   double za= ta->vertex[0][2];
82   double zb= tb->vertex[0][2];
83   return za > zb ? -1 :
84          za < zb ? +1 : 0;
85 }
86
87 static void sort_display_list(void) {
88   qsort(displaylist, ntris, sizeof(*displaylist), dl_compare);
89 }
90
91 /*---------- X stuff ----------*/
92
93 #define WSZ 400
94
95 static Display *display;
96 static Pixmap pixmap, doublebuffers[2];
97 static Window window;
98 static GC linegc, fillgc;
99 static int wwidth=WSZ, wheight=WSZ, wmindim=WSZ, wmaxdim=WSZ;
100 static int ncut, currentbuffer, x11depth, x11screen;
101
102 static double scale= 0.3;
103 static double eye_z= -10, eye_x= 0;
104 static double cut_z= -9;
105
106 static void drawtriangle(const Triangle *t) {
107   XPoint points[4];
108   int i;
109
110   for (i=0; i<3; i++) {
111     double *v= t->vertex[i];
112     double x= v[0];
113     double y= v[1];
114     double z= v[2];
115
116     if (z < cut_z) { ncut++; return; }
117     
118     double zezezp= eye_z / (eye_z - z);
119     points[i].x= scale * wmindim * (zezezp * (x - eye_x) + eye_x) + wwidth/2;
120     points[i].y= scale * wmindim * (zezezp *  y                 ) + wheight/2;
121   }
122   points[3]= points[0];
123
124   XA( XFillPolygon(display,pixmap,fillgc, points,3,Convex,CoordModeOrigin) );
125   XA( XDrawLines(display,pixmap,linegc,points, 4,CoordModeOrigin) );
126 }
127
128 static const unsigned long core_event_mask=
129   ButtonPressMask|ButtonReleaseMask|StructureNotifyMask|ButtonMotionMask;
130
131 static void mkpixmaps(void) {
132   for (currentbuffer=0; currentbuffer<2; currentbuffer++) {
133     XA( pixmap= XCreatePixmap(display,window,wwidth,wheight,x11depth) );
134     doublebuffers[currentbuffer]= pixmap;
135   }
136   currentbuffer= 0;
137 }
138
139 static void display_prepare(void) {
140   XGCValues gcv;
141   XSetWindowAttributes wa;
142   XVisualInfo vinfo;
143   XSizeHints hints;
144   
145   XA( display= XOpenDisplay(0) );
146   x11screen= DefaultScreen(display);
147   x11depth= DefaultDepth(display,x11screen);
148   XA( XMatchVisualInfo(display,x11screen,x11depth, TrueColor,&vinfo) );
149   
150   wa.event_mask= core_event_mask;
151   XA( window= XCreateWindow(display, DefaultRootWindow(display),
152                             0,0, wwidth,wheight, 0,x11depth,
153                             InputOutput, vinfo.visual,
154                             CWEventMask, &wa) );
155
156   hints.flags= USPosition;
157   hints.x= 10;
158   hints.y= 10;
159   XSetWMNormalHints(display,window,&hints);
160
161   mkpixmaps();
162
163   gcv.function= GXcopy;
164   gcv.plane_mask= ~0UL;
165   gcv.foreground= WhitePixel(display,x11screen);
166   linegc= XCreateGC(display,pixmap, GCFunction|GCPlaneMask|GCForeground, &gcv);
167   
168   gcv.function= GXclear;
169   gcv.plane_mask= ~0UL;
170   fillgc= XCreateGC(display,pixmap, GCFunction|GCPlaneMask, &gcv);
171 }
172
173 static void display_conformation(void) {
174   Triangle *const *t;
175   int i;
176   
177   pixmap= doublebuffers[currentbuffer];
178   XA( XFillRectangle(display,pixmap,fillgc,0,0,wwidth,wheight) );
179   for (i=0, t=displaylist, ncut=0; i<ntris; i++, t++)
180     drawtriangle(*t);
181   printf("shown, %d/%d triangles cut\n", ncut, ntris);
182   
183   XA( XSetWindowBackgroundPixmap(display,window,pixmap) );
184   XA( XClearWindow(display,window) );
185   currentbuffer= !currentbuffer;
186 }
187
188 static void show(void) {
189   read_input();
190   transform_coordinates();
191   generate_display_list();
192   sort_display_list();
193   display_conformation();
194 }
195
196 typedef struct {
197   const char *name;
198   void (*start)(void);
199   void (*delta)(double dx, double dy);
200   void (*conclude)(void);
201   void (*abandon)(void);
202 } Drag;
203
204 #define DRAG(x)                                 \
205   static const Drag drag_##x= {                 \
206     #x, drag_##x##_start, drag_##x##_delta,     \
207     drag_##x##_conclude, drag_##x##_abandon     \
208   }
209
210 #define DRAG_SAVING(x, thing)                           \
211   static typeof(thing) original_##thing;                \
212   static void drag_##x##_start(void) {                  \
213     memcpy(&original_##thing, &thing, sizeof(thing));   \
214   }                                                     \
215   static void drag_##x##_conclude(void) { }             \
216   static void drag_##x##_abandon(void) {                \
217     memcpy(&thing, &original_##thing, sizeof(thing));   \
218     show();                                             \
219   }                                                     \
220   DRAG(x)
221
222 static void drag_none_start(void) { }
223 static void drag_none_delta(double dx, double dy) { }
224 static void drag_none_conclude(void) { }
225 static void drag_none_abandon(void) { }
226 DRAG(none);
227
228 static void pvectorcore(const char *n, double v[D3]) {
229   int k;
230   printf("%10s [ ",n);
231   K printf("%# 10.10f ",v[k]);
232   printf("]\n");
233 }
234 static void pvector(const char *n, double v[D3]) {
235   pvectorcore(n,v);
236   putchar('\n');
237 }
238 static void pmatrix(const char *n, double m[D3][D3]) {
239   int j;
240   for (j=0; j<D3; j++) { pvectorcore(n,m[j]); n=""; }
241   putchar('\n');
242 }
243 #define PMATRIX(x) pmatrix(#x,x);
244
245 static void drag_rotate_delta(double dx, double dy) {
246   /* We multiple our transformation matrix by a matrix:
247    *
248    * If we just had y movement, we would rotate about x axis:
249    *  rotation X = [  1    0   0 ]
250    *               [  0   cy  sy ]
251    *               [  0  -sy  cy ]
252    *  where cy and sy are sin and cos of y rotation
253    *
254    * But we should pre-rotate this by a rotation about the z axis
255    * to get it to the right angle (to include x rotation).  So
256    * we make cy and sy be cos() and sin(hypot(x,y)) and use
257    * with cr,sr as cos() and sin(atan2(y,y)):
258    *
259    * Ie we would do  T' = R^T X R T   where
260    *             or  T' =    C    T   where  C = R^T X R  and
261    *
262    *  adjustment R = [  cr  sr  0 ]
263    *                 [ -sr  cr  0 ]
264    *                 [  0    0  1 ]
265    */
266
267   double rotx[D3][D3], adjr[D3][D3];
268   GSL_MATRIX(rotx);
269   GSL_MATRIX(adjr);
270
271   static double temp[D3][D3], change[D3][D3];
272   static GSL_MATRIX(temp);
273   static GSL_MATRIX(change);
274
275   double d= hypot(dx,dy);
276   if (d < 1e-6) return;
277
278   double ang= d*2.0;
279   
280   double cy= cos(ang);
281   double sy= sin(ang);
282   double cr= -dy / d;
283   double sr=  dx / d;
284   printf("\n d=%g cy,sy=%g,%g cr,sr=%g,%g\n\n", d,cy,sy,cr,sr);
285   
286   rotx[0][0]=   1;    rotx[0][1]=   0;     rotx[0][2]=  0;
287   rotx[1][0]=   0;    rotx[1][1]=  cy;     rotx[1][2]= sy;
288   rotx[2][0]=   0;    rotx[2][1]= -sy;     rotx[2][2]= cy;
289   PMATRIX(rotx);
290
291   adjr[0][0]=  cr;    adjr[0][1]=  sr;     adjr[0][2]=  0;
292   adjr[1][0]= -sr;    adjr[1][1]=  cr;     adjr[1][2]=  0;
293   adjr[2][0]=   0;    adjr[2][1]=   0;     adjr[2][2]=  1;
294   PMATRIX(adjr);
295
296   GA( gsl_blas_dgemm(CblasNoTrans,CblasNoTrans, 1.0,
297                      &rotx_gsl,&adjr_gsl,
298                      0.0, &temp_gsl) );
299   PMATRIX(temp);
300   
301   GA( gsl_blas_dgemm(CblasTrans,CblasNoTrans, 1.0,
302                      &adjr_gsl,&temp_gsl,
303                      0.0, &change_gsl) );
304   PMATRIX(change);
305
306   static double skew[D3][D3];
307   static GSL_MATRIX(skew);
308   
309   GA( gsl_blas_dgemm(CblasNoTrans,CblasNoTrans, 1.0,
310                      &change_gsl,&transform_gsl,
311                      0.0, &skew_gsl) );
312   PMATRIX(skew);
313
314   memcpy(&transform,&skew,sizeof(transform));
315   show();
316   return;
317
318   /* Now we want to normalise skew, the result becomes new transform */
319   double svd_v[D3][D3];
320   GSL_MATRIX(svd_v);
321
322   double sigma[D3], tau[D3];
323   GSL_VECTOR(sigma);
324   GSL_VECTOR(tau);
325   
326   /* We use notation from Wikipedia Polar_decomposition
327    *     Wikipedia's  W      is GSL's U
328    *     Wikipedia's  Sigma  is GSL's S
329    *     Wikipedia's  V      is GSL's V
330    *     Wikipedia's  U      is our desired result
331    * Wikipedia which says if the SVD is    A = W Sigma V*
332    * then the polar decomposition is       A = U P
333    *   where                               P = V Sigma V*
334    *   and                                 U = W V*
335    */
336   
337   GA( gsl_linalg_SV_decomp(&skew_gsl, &svd_v_gsl, &sigma_gsl, &tau_gsl) );
338   pmatrix("W",    skew);
339   pvector("Sigma",sigma);
340   pmatrix("V",    svd_v);
341
342   /* We only need U, not P. */
343   GA( gsl_blas_dgemm(CblasNoTrans,CblasTrans, 1.0,
344                      &skew_gsl,&svd_v_gsl,
345                      0.0,&transform_gsl) );
346
347   pmatrix("U", transform);
348
349   printf("drag_rotate_delta...\n");
350   show();
351 }
352 DRAG_SAVING(rotate, transform);
353
354 static void drag_scale_delta(double dx, double dy) {
355   scale *= pow(3.0, dy);
356   show();
357 }
358 DRAG_SAVING(scale, scale);
359
360 static const Drag *drag= &drag_none;
361
362 static int drag_last_x, drag_last_y;
363
364 static void drag_position(int x, int y) {
365   drag->delta((x - drag_last_x) * 1.0 / wmaxdim,
366               (y - drag_last_y) * 1.0 / wmaxdim);
367   drag_last_x= x;
368   drag_last_y= y;
369 }
370
371 static void event_button(XButtonEvent *e) {
372   if (e->window != window || !e->same_screen) return;
373   if (e->type == ButtonPress) {
374     if (e->state || drag != &drag_none) {
375       printf("drag=%s press state=0x%lx abandon\n",
376              drag->name, (unsigned long)e->state);
377       drag->abandon();
378       drag= &drag_none;
379       return;
380     }
381     switch (e->button) {
382     case Button1: drag= &drag_rotate;  break;
383     case Button2: drag= &drag_scale;   break;
384     default: printf("unknown drag start %d\n", e->button);
385     }
386     printf("drag=%s press button=%lu start %d,%d\n",
387            drag->name, (unsigned long)e->button, e->x, e->y);
388     drag_last_x= e->x;
389     drag_last_y= e->y;
390     drag->start();
391   }
392   if (e->type == ButtonRelease) {
393     printf("drag=%s release %d,%d\n", drag->name, e->x, e->y);
394     drag_position(e->x, e->y);
395     drag->conclude();
396     drag= &drag_none;
397   }
398 }
399
400 static void event_motion(int x, int y) {
401   printf("drag=%s motion %d,%d\n", drag->name, x, y);
402   drag_position(x,y);
403 }
404
405 static void event_config(XConfigureEvent *e) {
406   if (e->width == wwidth && e->height == wheight)
407     return;
408
409   wwidth= e->width;  wheight= e->height;
410   wmaxdim= wwidth > wheight ? wwidth : wheight;
411   wmindim= wwidth < wheight ? wwidth : wheight;
412   
413   XA( XSetWindowBackground(display,window,BlackPixel(display,x11screen)) );
414   for (currentbuffer=0; currentbuffer<2; currentbuffer++)
415     XA( XFreePixmap(display, doublebuffers[currentbuffer]) );
416
417   mkpixmaps();
418   show();
419 }
420
421 int main(int argc, const char *const *argv) {
422   XEvent event;
423   int k;
424   int motion_deferred=0, motion_x=-1, motion_y=-1;
425   
426   if (argc != 2 || argv[1][0]=='-') {
427     fputs("need filename\n",stderr); exit(8);
428   }
429   input_filename= argv[1];
430
431   read_input();
432   K transform[k][k]= 1.0;
433   display_prepare();
434   show();
435
436   XMapWindow(display,window);
437   for (;;) {
438     if (motion_deferred) {
439       int r= XCheckMaskEvent(display,~0UL,&event);
440       if (!r) {
441         event_motion(motion_x, motion_y);
442         motion_deferred=0;
443         continue;
444       }
445     } else {
446       XNextEvent(display,&event);
447     }
448     switch (event.type) {
449
450     case ButtonPress:
451     case ButtonRelease:     event_button(&event.xbutton);     break;
452       
453     case ConfigureNotify:   event_config(&event.xconfigure);  break;
454
455     case MotionNotify:
456       motion_x= event.xmotion.x;
457       motion_y= event.xmotion.y;
458       motion_deferred= 1;
459       continue;
460       
461     default:
462       printf("unknown event type %u 0x%x\n", event.type,event.type);
463     }
464   }
465 }
466