86f7ce69f5d6e45764e2871c7623b8f96d4d5807
[xscreensaver] / hacks / glx / winduprobot.c
1 /* winduprobot, Copyright (c) 2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Draws a robot wind-up toy.
12  *
13  * I've had this little robot since I was about six years old!  When the time
14  * came for us to throw the Cocktail Robotics Grand Challenge at DNA Lounge, I
15  * photographed this robot (holding a tiny martini glass) to make a flyer for
16  * the event.  You can see that photo here:
17  * http://www.dnalounge.com/flyers/2014/09/14.html
18  *
19  * Then I decided to try and make award statues for the contest by modeling
20  * this robot and 3D-printing it (a robot on a post, with the DNA Lounge
21  * grommet around it.)  So I learned Maya and built a model.
22  *
23  * Well, that 3D printing idea didn't work out, but since I had the model
24  * already, I exported it and turned it into a screen saver.
25  *
26  * The DXF files that Maya exports aren't simple enough for my dxf2gl.pl
27  * script to process, so the exporting process went:
28  *
29  *  - Save from Maya to OBJ;
30  *  - Import OBJ into SketchUp using
31  *    http://www.scriptspot.com/sketchup/scripts/obj-importer
32  *  - Clean up the model a little bit more;
33  *  - Export to DXF with "Millimeters", "Triangles", using
34  *    http://www.guitar-list.com/download-software/convert-sketchup-skp-files-dxf-or-stl
35  */
36
37 #define LABEL_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
38
39 #define DEFAULTS        "*delay:        20000       \n" \
40                         "*count:        25          \n" \
41                         "*showFPS:      False       \n" \
42                         "*wireframe:    False       \n" \
43                         "*labelFont:  " LABEL_FONT "\n" \
44                         "*legColor:     #AA2222"   "\n" \
45                         "*armColor:     #AA2222"   "\n" \
46                         "*handColor:    #AA2222"   "\n" \
47                         "*crankColor:   #444444"   "\n" \
48                         "*bodyColor:    #7777AA"   "\n" \
49                         "*domeColor:    #7777AA"   "\n" \
50                         "*insideColor:  #DDDDDD"   "\n" \
51                         "*gearboxColor: #444488"   "\n" \
52                         "*gearColor:    #008877"   "\n" \
53                         "*wheelColor:   #007788"   "\n" \
54                         "*wireColor:    #006600"   "\n" \
55                         "*groundColor:  #0000FF"   "\n" \
56                         "*textColor:       #FFFFFF""\n" \
57                         "*textBackground:  #444444""\n" \
58                         "*textBorderColor: #FFFF88""\n" \
59                         "*textLines:    10          \n" \
60                         "*program:      xscreensaver-text\n" \
61                         "*usePty:       False\n"
62
63 #undef DEBUG
64 #define WORDBUBBLES
65
66 # define refresh_robot 0
67 #undef countof
68 #define countof(x) (sizeof((x))/sizeof((*x)))
69
70 #define DEF_SPEED       "1.0"
71 #define DEF_ROBOT_SIZE  "1.0"
72 #define DEF_TEXTURE     "True"
73 #define DEF_FADE        "True"
74 #define DEF_OPACITY     "1.0"
75 #define DEF_TALK        "0.2"
76
77 #include "xlockmore.h"
78 #include "gltrackball.h"
79 #include "xpm-ximage.h"
80 #include "involute.h"
81 #include "sphere.h"
82
83 #ifdef WORDBUBBLES
84 # include "textclient.h"
85 # include "texfont.h"
86 #endif
87
88 #include <ctype.h>
89
90 #ifndef HAVE_JWZGLES /* No SPHERE_MAP on iPhone */
91 # define HAVE_TEXTURE
92 #endif
93
94 #ifdef HAVE_TEXTURE
95 # include "../images/chromesphere.xpm"
96 #endif
97
98 #ifdef USE_GL /* whole file */
99
100 #include "gllist.h"
101
102 extern const struct gllist
103   *robot_arm_half, *robot_body_half_outside, *robot_body_half_inside,
104   *robot_crank_full, *robot_gearbox_half, *robot_hand_half,
105   *robot_leg_half, *robot_rotator_half, *robot_wireframe;
106
107 static struct gllist *robot_dome = 0, *robot_gear = 0, *ground = 0;
108
109 static const struct gllist * const *all_objs[] = {
110   &robot_arm_half, &robot_body_half_outside, &robot_body_half_inside,
111   &robot_crank_full, &robot_gearbox_half, &robot_hand_half,
112   &robot_leg_half, &robot_rotator_half, &robot_wireframe,
113   (const struct gllist * const *) &robot_dome,
114   (const struct gllist * const *) &robot_gear,
115   (const struct gllist * const *) &ground
116 };
117
118 #define ROBOT_ARM       0
119 #define ROBOT_BODY_1    1
120 #define ROBOT_BODY_2    2
121 #define ROBOT_CRANK     3
122 #define ROBOT_GEARBOX   4
123 #define ROBOT_HAND      5
124 #define ROBOT_LEG       6
125 #define ROBOT_ROTATOR   7
126 #define ROBOT_WIREFRAME 8
127 #define ROBOT_DOME      9
128 #define ROBOT_GEAR      10
129 #define GROUND          11
130
131 typedef struct {
132   GLfloat x, y, z;              /* position */
133   GLfloat facing;               /* direction of front of robot, degrees */
134   GLfloat pitch;                /* front/back tilt angle, degrees */
135   GLfloat roll;                 /* left/right tilt angle, degrees */
136   GLfloat speed;                /* some robots are faster */
137   GLfloat crank_rot;            /* gear state, degrees */
138   GLfloat hand_rot[2];          /* rotation of the hands, degrees */
139   GLfloat hand_pos[2];          /* openness of the hands, ratio */
140   GLfloat balance;              /* how off-true does it walk? degrees */
141   GLfloat body_transparency;    /* ratio */
142   int fading_p;                 /* -1, 0, 1 */
143
144 } walker;
145
146 typedef struct {
147   GLXContext *glx_context;
148   trackball_state *user_trackball;
149   Bool button_down_p;
150
151   GLuint *dlists;
152   GLfloat component_colors[countof(all_objs)][4];
153
154   int nwalkers;
155   walker *walkers;
156
157   GLfloat looking_x, looking_y, looking_z;      /* Where camera is aimed */
158   GLfloat olooking_x, olooking_y, olooking_z;   /* Where camera was aimed */
159   Bool camera_tracking_p;                       /* Whether camera in motion */
160   GLfloat tracking_ratio;
161
162 # ifdef HAVE_TEXTURE
163   GLuint chrome_texture;
164 # endif
165
166 # ifdef WORDBUBBLES
167   texture_font_data *font_data;
168   int bubble_tick;
169   text_data *tc;
170   char words[10240];
171   int lines, max_lines;
172
173   GLfloat text_color[4], text_bg[4], text_bd[4];
174
175 # endif /* WORDBUBBLES */
176
177
178 # ifdef DEBUG
179   GLfloat debug_x, debug_y, debug_z;
180 # endif
181
182 } robot_configuration;
183
184 static robot_configuration *bps = NULL;
185
186 static GLfloat speed, size, opacity;
187 static int do_texture, do_fade;
188 #ifdef WORDBUBBLES
189 static GLfloat talk_chance;
190 #endif
191 #ifdef DEBUG
192 static int debug_p;
193 #endif
194
195 static XrmOptionDescRec opts[] = {
196   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
197   { "-size",       ".robotSize", XrmoptionSepArg, 0 },
198   { "-opacity",    ".opacity",   XrmoptionSepArg, 0 },
199   { "-talk",       ".talk",      XrmoptionSepArg, 0 },
200   {"-texture",     ".texture",   XrmoptionNoArg, "True" },
201   {"+texture",     ".texture",   XrmoptionNoArg, "False" },
202   {"-fade",        ".fade",      XrmoptionNoArg, "True" },
203   {"+fade",        ".fade",      XrmoptionNoArg, "False" },
204 #ifdef DEBUG
205   {"-debug",       ".debug",     XrmoptionNoArg, "True" },
206   {"+debug",       ".debug",     XrmoptionNoArg, "False" },
207 #endif
208 };
209
210 static argtype vars[] = {
211   {&speed,       "speed",      "Speed",     DEF_SPEED,      t_Float},
212   {&size,        "robotSize",  "RobotSize", DEF_ROBOT_SIZE, t_Float},
213   {&opacity,     "opacity",    "Opacity",   DEF_OPACITY,    t_Float},
214   {&do_texture,  "texture",    "Texture",   DEF_TEXTURE,    t_Bool},
215   {&do_fade,     "fade",       "Fade",      DEF_FADE,       t_Bool},
216 #ifdef WORDBUBBLES
217   {&talk_chance, "talk",       "Talk",      DEF_TALK,       t_Float},
218 #endif
219 #ifdef DEBUG
220   {&debug_p,     "debug",      "Debug",     "False",        t_Bool},
221 #endif
222 };
223
224 ENTRYPOINT ModeSpecOpt robot_opts = {
225   countof(opts), opts, countof(vars), vars, NULL};
226
227
228 /* Window management, etc
229  */
230 ENTRYPOINT void
231 reshape_robot (ModeInfo *mi, int width, int height)
232 {
233   GLfloat h = (GLfloat) height / (GLfloat) width;
234
235   glViewport (0, 0, (GLint) width, (GLint) height);
236
237   glMatrixMode(GL_PROJECTION);
238   glLoadIdentity();
239   gluPerspective (40.0, 1/h, 1.0, 250);
240
241   glMatrixMode(GL_MODELVIEW);
242   glLoadIdentity();
243   gluLookAt( 0, 20, 30,
244              0, 0, 0,
245              0, 1, 0);
246
247   glClear(GL_COLOR_BUFFER_BIT);
248
249 # ifdef WORDBUBBLES
250   {
251     robot_configuration *bp = &bps[MI_SCREEN(mi)];
252     int w = (width < 800 ? 25 : 40);
253     int h = 1000;
254     if (bp->tc)
255       textclient_reshape (bp->tc, w, h, w, h,
256                           /* Passing bp->max_lines isn't actually necessary */
257                           0);
258   }
259 # endif
260
261 }
262
263
264 ENTRYPOINT Bool
265 robot_handle_event (ModeInfo *mi, XEvent *event)
266 {
267   robot_configuration *bp = &bps[MI_SCREEN(mi)];
268
269   if (gltrackball_event_handler (event, bp->user_trackball,
270                                  MI_WIDTH (mi), MI_HEIGHT (mi),
271                                  &bp->button_down_p))
272     return True;
273 #ifdef DEBUG
274   else if (event->xany.type == KeyPress && debug_p)
275     {
276       KeySym keysym;
277       char c = 0;
278       double n[3] = { 1.0, 0.1, 0.1 };
279       int s = (event->xkey.state & ShiftMask ? 10 : 1);
280
281       XLookupString (&event->xkey, &c, 1, &keysym, 0);
282
283       if      (keysym == XK_Right)   bp->debug_x += n[0] * s;
284       else if (keysym == XK_Left)    bp->debug_x -= n[0] * s;
285       else if (keysym == XK_Up)      bp->debug_y += n[1] * s;
286       else if (keysym == XK_Down)    bp->debug_y -= n[1] * s;
287       else if (c == '=' || c == '+') bp->debug_z += n[2] * s;
288       else if (c == '-' || c == '_') bp->debug_z -= n[2] * s;
289       else if (c == ' ') bp->debug_x = bp->debug_y = bp->debug_z = 0;
290       else if (c == '\n' || c == '\r')
291         fprintf (stderr, "%.4f %.4f %.4f\n", 
292                  bp->debug_x, bp->debug_y, bp->debug_z);
293       else return False;
294       return True;
295     }
296 #endif /* DEBUG */
297
298   return False;
299 }
300
301
302 #ifdef HAVE_TEXTURE
303
304 static void
305 load_textures (ModeInfo *mi)
306 {
307   robot_configuration *bp = &bps[MI_SCREEN(mi)];
308   XImage *xi;
309
310   xi = xpm_to_ximage (mi->dpy, mi->xgwa.visual, mi->xgwa.colormap,
311                       chromesphere_xpm);
312   clear_gl_error();
313
314   glGenTextures (1, &bp->chrome_texture);
315   glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
316   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
317   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
318   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
319   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
320                 xi->width, xi->height, 0,
321                 GL_RGBA,
322 # ifndef USE_IPHONE
323                 GL_UNSIGNED_INT_8_8_8_8_REV,
324 # else
325                 GL_UNSIGNED_BYTE,
326 # endif
327                 xi->data);
328   check_gl_error("texture");
329
330   glEnable(GL_TEXTURE_GEN_S);
331   glEnable(GL_TEXTURE_GEN_T);
332   glEnable(GL_TEXTURE_2D);
333 }
334
335 #endif /* HAVE_TEXTURE */
336
337
338 static int unit_gear (ModeInfo *, GLfloat color[4]);
339 static int draw_ground (ModeInfo *, GLfloat color[4]);
340 static void init_walker (ModeInfo *, walker *);
341
342 static void
343 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
344 {
345   XColor xcolor;
346   char *string = get_string_resource (mi->dpy, key, "RobotColor");
347   if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
348     {
349       fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
350                key, string);
351       exit (1);
352     }
353
354   color[0] = xcolor.red   / 65536.0;
355   color[1] = xcolor.green / 65536.0;
356   color[2] = xcolor.blue  / 65536.0;
357   color[3] = 1;
358 }
359
360
361 ENTRYPOINT void 
362 init_robot (ModeInfo *mi)
363 {
364   robot_configuration *bp;
365   int wire = MI_IS_WIREFRAME(mi);
366   int i;
367   if (!bps) {
368     bps = (robot_configuration *)
369       calloc (MI_NUM_SCREENS(mi), sizeof (robot_configuration));
370     if (!bps) {
371       fprintf(stderr, "%s: out of memory\n", progname);
372       exit(1);
373     }
374   }
375
376   bp = &bps[MI_SCREEN(mi)];
377
378   bp->glx_context = init_GL(mi);
379
380   reshape_robot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
381
382   glShadeModel(GL_SMOOTH);
383
384   glEnable(GL_DEPTH_TEST);
385   glEnable(GL_NORMALIZE);
386   glEnable(GL_CULL_FACE);
387
388   if (!wire)
389     {
390       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
391 /*      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
392       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
393       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
394       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
395
396       glEnable(GL_LIGHTING);
397       glEnable(GL_LIGHT0);
398       glEnable(GL_DEPTH_TEST);
399       glEnable(GL_CULL_FACE);
400
401       glLightfv(GL_LIGHT0, GL_POSITION, pos);
402       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
403       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
404       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
405
406       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
407     }
408
409 # ifdef HAVE_TEXTURE
410   if (!wire && do_texture)
411     load_textures (mi);
412 # endif
413
414   bp->user_trackball = gltrackball_init (False);
415
416   bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
417   for (i = 0; i < countof(all_objs); i++)
418     bp->dlists[i] = glGenLists (1);
419
420   for (i = 0; i < countof(all_objs); i++)
421     {
422       const struct gllist *gll = *all_objs[i];
423       char *key = 0;
424       GLfloat spec1[4] = {1.00, 1.00, 1.00, 1.0};
425       GLfloat spec2[4] = {0.40, 0.40, 0.70, 1.0};
426       GLfloat *spec = spec1;
427       GLfloat shiny = 20;
428
429       glNewList (bp->dlists[i], GL_COMPILE);
430
431       glMatrixMode(GL_MODELVIEW);
432       glPushMatrix();
433       glMatrixMode(GL_TEXTURE);
434       glPushMatrix();
435       glMatrixMode(GL_MODELVIEW);
436
437       glRotatef (-90, 1, 0, 0);
438       glRotatef (180, 0, 0, 1);
439       glScalef (6, 6, 6);
440
441       glBindTexture (GL_TEXTURE_2D, 0);
442
443       switch (i) {
444       case ROBOT_BODY_1:
445         key = "bodyColor";
446         spec = spec1;
447         shiny = 128;
448 # ifdef HAVE_TEXTURE
449         if (do_texture)
450           {
451             glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
452             glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
453             glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
454           }
455 # endif
456         break;
457       case ROBOT_DOME:
458         key = "domeColor";
459         spec = spec1;
460         shiny = 128;
461 # ifdef HAVE_TEXTURE
462         if (do_texture)
463           {
464             glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
465             glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
466             glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
467           }
468 # endif
469         break;
470       case ROBOT_BODY_2:
471         key = "insideColor";
472         spec = spec2;
473         shiny = 128;
474         break;
475       case ROBOT_ARM:
476         key = "armColor";
477         spec = spec2;
478         shiny = 20;
479         break;
480       case ROBOT_HAND:
481         key = "handColor";
482         spec = spec2;
483         shiny = 20;
484         break;
485       case ROBOT_LEG:
486         key = "legColor";
487         spec = spec2;
488         shiny = 20;
489         break;
490       case ROBOT_CRANK:
491         key = "crankColor";
492         spec = spec2;
493         shiny = 20;
494         break;
495       case ROBOT_ROTATOR:
496         key = "wheelColor";
497         spec = spec2;
498         shiny = 20;
499         break;
500       case ROBOT_GEAR:
501         key = "gearColor";
502         spec = spec2;
503         shiny = 20;
504         break;
505       case ROBOT_GEARBOX:
506         key = "gearboxColor";
507         spec = spec2;
508         shiny = 20;
509         break;
510       case ROBOT_WIREFRAME:
511         key = "wireColor";
512         spec = spec2;
513         shiny = 20;
514         break;
515       case GROUND:
516         key = "groundColor";
517         spec = spec2;
518         shiny = 20;
519         break;
520       default:
521         abort();
522       }
523
524       parse_color (mi, key, bp->component_colors[i]);
525
526       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  spec);
527       glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
528
529       switch (i) {
530       case ROBOT_DOME:
531         if (! robot_dome)
532           robot_dome = (struct gllist *) calloc (1, sizeof(*robot_dome));
533         robot_dome->points = unit_dome (32, 32, MI_IS_WIREFRAME(mi));
534         break;
535       case ROBOT_GEAR:
536         if (! robot_gear)
537           robot_gear = (struct gllist *) calloc (1, sizeof(*robot_gear));
538         robot_gear->points = unit_gear (mi, bp->component_colors[i]);
539         break;
540       case GROUND:
541         if (! ground)
542           ground = (struct gllist *) calloc (1, sizeof(*ground));
543         ground->points = draw_ground (mi, bp->component_colors[i]);
544         break;
545       case ROBOT_WIREFRAME:
546         glLineWidth (0.3);
547         renderList (gll, True);
548         break;
549       default:
550         renderList (gll, wire);
551         /* glColor3f (1, 1, 1); renderListNormals (gll, 100, True); */
552         /* glColor3f (1, 1, 0); renderListNormals (gll, 100, False); */
553         break;
554       }
555
556       glMatrixMode(GL_TEXTURE);
557       glPopMatrix();
558       glMatrixMode(GL_MODELVIEW);
559       glPopMatrix();
560
561       glEndList ();
562     }
563
564 # ifdef DEBUG
565   if (debug_p) MI_COUNT(mi) = 1;
566 # endif
567
568   bp->nwalkers = MI_COUNT(mi);
569   bp->walkers = (walker *) calloc (bp->nwalkers, sizeof (walker));
570
571   for (i = 0; i < bp->nwalkers; i++)
572     init_walker (mi, &bp->walkers[i]);
573
574   /* Since #0 is the one we track, make sure it doesn't walk too straight.
575    */
576   bp->walkers[0].balance *= 1.5;
577
578 # ifdef WORDBUBBLES
579   bp->font_data = load_texture_font (mi->dpy, "labelFont");
580   bp->max_lines = get_integer_resource (mi->dpy, "textLines", "TextLines");
581   bp->tc = textclient_open (MI_DISPLAY (mi));
582
583   parse_color (mi, "textColor", bp->text_color);
584   parse_color (mi, "textBackground", bp->text_bg);
585   parse_color (mi, "textBorderColor", bp->text_bd);
586
587   reshape_robot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
588
589 # endif /* WORDBUBBLES */
590
591   /* Let's tilt the floor a little. */
592 # ifdef DEBUG
593   if (!debug_p)
594 # endif
595     {
596       gltrackball_start (bp->user_trackball, 0, 500,  1000, 1000);
597       gltrackball_track (bp->user_trackball,
598                          0, 500 + (random() % 200) - 100,
599                          1000, 1000);
600     }
601 }
602
603
604 static int
605 draw_component (ModeInfo *mi, int i)
606 {
607   robot_configuration *bp = &bps[MI_SCREEN(mi)];
608   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
609                 bp->component_colors[i]);
610   glCallList (bp->dlists[i]);
611   return (*all_objs[i])->points / 3;
612 }
613
614 static int
615 draw_transparent_component (ModeInfo *mi, int i, GLfloat alpha)
616 {
617   robot_configuration *bp = &bps[MI_SCREEN(mi)];
618   int wire = MI_IS_WIREFRAME(mi);
619   int count = 0;
620
621   if (alpha < 0) return 0;
622   if (alpha > 1) alpha = 1;
623   bp->component_colors[i][3] = alpha;
624
625   if (wire || alpha >= 1)
626     return draw_component (mi, i);
627
628   /* Draw into the depth buffer but not the frame buffer */
629   glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
630   count += draw_component (mi, i);
631
632   /* Now draw into the frame buffer only where there's already depth */
633   glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
634   glDepthFunc (GL_EQUAL);
635   glEnable (GL_BLEND);
636   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
637
638   count += draw_component (mi, i);
639   glDepthFunc (GL_LESS);
640   glDisable (GL_BLEND);
641   return count;
642 }
643
644
645
646 static int
647 draw_arm_half (ModeInfo *mi, walker *f)
648 {
649   int count = 0;
650   glPushMatrix();
651   count += draw_transparent_component (mi, ROBOT_ARM, f->body_transparency);
652   glPopMatrix();
653   return count;
654 }
655
656 static int
657 draw_hand_half (ModeInfo *mi, walker *f)
658 {
659   int count = 0;
660   glPushMatrix();
661   count += draw_transparent_component (mi, ROBOT_HAND, f->body_transparency);
662   glPopMatrix();
663   return count;
664 }
665
666
667 /* rotation of arm: 0-360.
668    openness of fingers: 0.0 - 1.0.
669  */
670 static int
671 draw_arm (ModeInfo *mi, walker *f, GLfloat left_p, GLfloat rot, GLfloat open)
672 {
673   int count = 0;
674   GLfloat arm_x = 4766; /* distance from origin to arm axis */
675   GLfloat arm_y = 12212;
676
677   open *= 5.5;   /* scale of finger range */
678
679 # ifdef DEBUG
680   if (debug_p) rot = 0;
681 # endif
682
683   glPushMatrix();
684
685   if (! left_p)
686     glTranslatef (0, 0, arm_x * 2);
687
688   glTranslatef (0, arm_y, -arm_x);      /* move to origin */
689   glRotatef (rot, 1, 0, 0);
690   glTranslatef (0, -arm_y, arm_x);      /* move back */
691
692   glFrontFace(GL_CCW);
693   count += draw_arm_half (mi, f);
694
695   glScalef (1, -1, 1);
696   glTranslatef (0, -arm_y * 2, 0);
697   glFrontFace(GL_CW);
698   count += draw_arm_half (mi, f);
699
700   glPushMatrix();
701   glTranslatef (0, 0, -arm_x * 2);
702   glScalef (1, 1, -1);
703   glFrontFace(GL_CCW);
704   count += draw_arm_half (mi, f);
705
706   glScalef (1, -1, 1);
707   glTranslatef (0, -arm_y * 2, 0);
708   glFrontFace(GL_CW);
709   count += draw_arm_half (mi, f);
710   glPopMatrix();
711
712   glTranslatef (0, 0, open);
713   glFrontFace(GL_CW);
714   count += draw_hand_half (mi, f);
715
716   glTranslatef (0, 0, -open);
717   glScalef (1, 1, -1);
718   glTranslatef (0, 0, arm_x * 2);
719   glFrontFace(GL_CCW);
720   glTranslatef (0, 0, open);
721   count += draw_hand_half (mi, f);
722
723   glPopMatrix();
724   return count;
725 }
726
727
728 static int
729 draw_body_half (ModeInfo *mi, walker *f, Bool inside_p)
730 {
731   int count = 0;
732   int which = (inside_p ? ROBOT_BODY_2 : ROBOT_BODY_1);
733   glPushMatrix();
734   count += draw_transparent_component (mi, which, f->body_transparency);
735   glPopMatrix();
736   return count;
737 }
738
739
740 static int
741 draw_body (ModeInfo *mi, walker *f, Bool inside_p)
742 {
743   int count = 0;
744   glPushMatrix();
745
746   glFrontFace(GL_CCW);
747   count += draw_body_half (mi, f, inside_p);
748
749   glScalef (1, 1, -1);
750   glFrontFace(GL_CW);
751   count += draw_body_half (mi, f, inside_p);
752
753   glPopMatrix();
754
755   return count;
756 }
757
758
759 static int
760 draw_gearbox_half (ModeInfo *mi)
761 {
762   int count = 0;
763   glPushMatrix();
764   count += draw_component (mi, ROBOT_GEARBOX);
765   glPopMatrix();
766   return count;
767 }
768
769
770 static int
771 draw_gearbox (ModeInfo *mi)
772 {
773   int count = 0;
774   glPushMatrix();
775
776   glFrontFace(GL_CCW);
777   count += draw_gearbox_half (mi);
778
779   glScalef (1, 1, -1);
780   glFrontFace(GL_CW);
781   count += draw_gearbox_half (mi);
782
783   glPopMatrix();
784   return count;
785 }
786
787
788 static int
789 unit_gear (ModeInfo *mi, GLfloat color[4])
790 {
791   int wire = MI_IS_WIREFRAME(mi);
792   gear G = { 0, };
793   gear *g = &G;
794
795   g->r          = 0.5;
796   g->nteeth     = 16;
797   g->tooth_h    = 0.12;
798   g->thickness  = 0.32;
799   g->thickness2 = g->thickness * 0.5;
800   g->thickness3 = g->thickness;
801   g->inner_r    = g->r * 0.7;
802   g->inner_r2   = g->r * 0.4;
803   g->inner_r3   = g->r * 0.1;
804   g->size       = INVOLUTE_LARGE;
805
806   g->color[0] = g->color2[0] = color[0];
807   g->color[1] = g->color2[1] = color[1];
808   g->color[2] = g->color2[2] = color[2];
809   g->color[3] = g->color2[3] = color[3];
810
811   return draw_involute_gear (g, wire);
812 }
813
814
815 static int
816 draw_gear (ModeInfo *mi)
817 {
818   int count = 0;
819   GLfloat n = 350;
820   glScalef (n, n, n);
821   count += draw_component (mi, ROBOT_GEAR);
822   return count;
823 }
824
825
826 static int
827 draw_crank (ModeInfo *mi, walker *f, GLfloat rot)
828 {
829   int count = 0;
830   GLfloat origin = 12210.0;
831
832   rot = -rot;
833
834   glPushMatrix();
835
836   glTranslatef (0, origin, 0);   /* position at origin */
837   glRotatef (rot, 0, 0, 1);
838
839   glPushMatrix();
840   glRotatef (90, 1, 0, 0);
841   count += draw_gear (mi);
842   glPopMatrix();
843
844   glTranslatef (0, -origin, 0);  /* move back */
845
846   glFrontFace(GL_CCW);
847   count += draw_component (mi, ROBOT_CRANK);
848
849   glPopMatrix();
850
851   return count;
852 }
853
854
855 static int
856 draw_rotator_half (ModeInfo *mi)
857 {
858   int count = 0;
859   glPushMatrix();
860   count += draw_component (mi, ROBOT_ROTATOR);
861   glPopMatrix();
862   return count;
863 }
864
865
866 static int
867 draw_rotator (ModeInfo *mi, walker *f, GLfloat rot)
868 {
869   int count = 0;
870   GLfloat origin = 10093.0;
871
872   glPushMatrix();
873
874   glTranslatef (0, origin, 0);   /* position at origin */
875   glRotatef (rot, 0, 0, 1);
876
877   glPushMatrix();
878   glRotatef (90, 1, 0, 0);
879   count += draw_gear (mi);
880   glPopMatrix();
881
882 # ifdef DEBUG
883   if (debug_p)
884     {
885       glDisable(GL_LIGHTING);
886       glBegin(GL_LINES);
887       glVertex3f(0, 0, -3000);
888       glVertex3f(0, 0,  3000);
889       glEnd();
890       if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
891     }
892 # endif
893
894   glTranslatef (0, -origin, 0);   /* move back */
895
896   glFrontFace(GL_CCW);
897   count += draw_rotator_half (mi);
898
899   glScalef (1, 1, -1);
900   glFrontFace(GL_CW);
901   glRotatef (180, 0, 0, 1);
902   glTranslatef (0, -origin * 2, 0);   /* move back */
903   count += draw_rotator_half (mi);
904
905   glPopMatrix();
906   return count;
907 }
908
909
910 static int
911 draw_leg_half (ModeInfo *mi)
912 {
913   int count = 0;
914   glPushMatrix();
915   count += draw_component (mi, ROBOT_LEG);
916   glPopMatrix();
917   return count;
918 }
919
920
921 static int
922 draw_wireframe (ModeInfo *mi, walker *f)
923 {
924   int wire = MI_IS_WIREFRAME(mi);
925   int count = 0;
926   GLfloat alpha = 0.6 - f->body_transparency;
927   if (alpha < 0) return 0;
928   alpha *= 0.3;
929   if (!wire) glDisable (GL_LIGHTING);
930   glPushMatrix();
931   count += draw_transparent_component (mi, ROBOT_WIREFRAME, alpha);
932   glPopMatrix();
933   if (!wire) glEnable (GL_LIGHTING);
934   return count;
935 }
936
937
938 static int
939 draw_leg (ModeInfo *mi, GLfloat rot, Bool left_p)
940 {
941   int count = 0;
942   GLfloat x, y;
943   GLfloat leg_distance = 9401;   /* distance from ground to leg axis */
944   GLfloat rot_distance = 10110;  /* distance from ground to rotator axis */
945   GLfloat pin_distance = 14541;  /* distance from ground to stop pin */
946   GLfloat orbit_r = rot_distance - leg_distance;  /* radius of rotation */
947
948   /* Actually it's the bottom of the pin minus its diameter, or something. */
949   pin_distance -= 590;
950
951   glPushMatrix();
952
953   if (left_p)
954     glRotatef (180, 0, 1, 0);
955
956   if (!left_p) rot = -(rot + 180);
957
958   rot -= 90;
959
960   x = orbit_r * cos (-rot * M_PI / 180);
961   y = orbit_r * sin (-rot * M_PI / 180);
962
963   {
964     /* Rotate the leg by angle B of the right                 A
965        triangle ABC, where:                                  /:
966                                                             / :
967         A = position of stop pin                           /  :
968         D = position of rotator wheel's axis            , - ~ ~ ~ - ,
969         C = D + y                                   , '  /    :       ' ,
970         B = D + xy (leg's axis)                   ,     /     :           ,
971                                                  ,     /      :            ,
972       So:                                       ,     /       :             ,
973         H = dist(A,B)                           ,    /        D             ,
974         O = dist(A,C)                           ,   /         :             ,
975         sin(th) = O/H                            , /          :            ,
976         th = asin(O/H)                            B ~ ~ ~ ~ ~ C           .
977                                                     ,                  , '
978                                                       ' - , _ _ _ ,  '
979     */
980     GLfloat Ay = pin_distance - leg_distance;
981     GLfloat Cx = 0, Cy = y;
982     GLfloat Bx = x;
983     GLfloat dBC = Cx - Bx;
984     GLfloat dAC = Cy - Ay;
985     GLfloat dAB = sqrt (dBC*dBC + dAC*dAC);
986     GLfloat th = asin (dAC / dAB);
987     rot = th / (M_PI / 180.0);
988     rot += 90;
989     if (dBC > 0) rot = 360-rot;
990   }
991
992   glTranslatef (0, orbit_r, 0);        /* position on rotator axis */
993   glTranslatef (x, y, 0);
994
995   glTranslatef (0, leg_distance, 0);   /* position on leg axis */
996   glRotatef(rot, 0, 0, 1);
997   glTranslatef (0, -leg_distance, 0);  /* move back */
998
999   glFrontFace(GL_CCW);
1000   count += draw_leg_half (mi);
1001
1002   glScalef (-1, 1, 1);
1003   glFrontFace(GL_CW);
1004   count += draw_leg_half (mi);
1005
1006   glPopMatrix();
1007   return count;
1008 }
1009
1010
1011 static int
1012 draw_dome (ModeInfo *mi, walker *f)
1013 {
1014   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1015   int wire = MI_IS_WIREFRAME(mi);
1016   int which = ROBOT_DOME;
1017   int count = 0;
1018   GLfloat n = 8.3;
1019   GLfloat trans = f->body_transparency;
1020   GLfloat max = 0.7;
1021   GLfloat dome_y = 15290;
1022
1023   if (trans < 0) trans = 0;
1024   if (trans > max) trans = max;
1025
1026   if (!wire) glEnable (GL_BLEND);
1027
1028   glPushMatrix();
1029   glTranslatef (0, dome_y, 0);
1030   glScalef (100, 100, 100);
1031   glRotatef (90, 1, 0, 0);
1032   glTranslatef (0.35, 0, 0);
1033   glScalef (n, n, n);
1034   glFrontFace(GL_CCW);
1035   bp->component_colors[which][3] = trans;
1036   count += draw_component (mi, which);
1037   glPopMatrix();
1038
1039   if (!wire) glDisable (GL_BLEND);
1040
1041   return count;
1042 }
1043
1044
1045 /* Is this robot overlapping any other?
1046  */
1047 static Bool
1048 collision_p (ModeInfo *mi, walker *w, GLfloat extra_space)
1049 {
1050   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1051   int i;
1052   if (MI_COUNT(mi) <= 1) return False;
1053
1054   for (i = 0; i < MI_COUNT(mi); i++)
1055     {
1056       walker *w2 = &bp->walkers[i];
1057       GLfloat min = 0.75 + extra_space;
1058       GLfloat d, dx, dy;
1059       if (w == w2) continue;
1060       dx = w->x - w2->x;
1061       dy = w->y - w2->y;
1062       d = (dx*dx + dy*dy);
1063       if (d <= min*min) return True;
1064     }
1065   return False;
1066 }
1067
1068
1069 /* I couldn't figure out a straightforward trig solution to the
1070    forward/backward tilting that happens as weight passes from one
1071    foot to another, so I just built a tool to eyeball it manually.
1072    { vertical_translation, rotation, forward_translation }
1073  */
1074 static const struct { GLfloat up, rot, fwd; } wobble_profile[360] = {
1075  {  0.0000,  0.00, 0.0000 },    /* 0 */
1076  {  0.0000, -0.01, 0.0025 },
1077  {  0.0000, -0.25, 0.0040 },
1078  {  0.0000, -0.41, 0.0060 },
1079  {  0.0000, -0.62, 0.0080 },
1080  {  0.0000, -0.80, 0.0095 },
1081  {  0.0000, -0.90, 0.0120 },
1082  {  0.0000, -1.10, 0.0135 },
1083  {  0.0000, -1.25, 0.0150 },
1084  {  0.0000, -1.40, 0.0175 },
1085  {  0.0000, -1.50, 0.0195 },    /* 10 */
1086  {  0.0000, -1.70, 0.0215 },
1087  {  0.0000, -1.80, 0.0230 },
1088  {  0.0000, -2.00, 0.0250 },
1089  {  0.0000, -2.10, 0.0270 },
1090  { -0.0005, -2.30, 0.0290 },
1091  { -0.0005, -2.50, 0.0305 },
1092  { -0.0005, -2.60, 0.0330 },
1093  { -0.0005, -2.70, 0.0330 },
1094  { -0.0005, -2.90, 0.0350 },
1095  { -0.0005, -3.00, 0.0365 },    /* 20 */
1096  { -0.0010, -3.20, 0.0380 },
1097  { -0.0005, -3.30, 0.0400 },
1098  { -0.0010, -3.50, 0.0420 },
1099  { -0.0010, -3.70, 0.0440 },
1100  { -0.0010, -3.80, 0.0460 },
1101  { -0.0010, -3.90, 0.0470 },
1102  { -0.0015, -4.10, 0.0500 },
1103  { -0.0015, -4.20, 0.0515 },
1104  { -0.0015, -4.40, 0.0535 },
1105  { -0.0015, -4.50, 0.0550 },    /* 30 */
1106  { -0.0015, -4.60, 0.0565 },
1107  { -0.0020, -4.80, 0.0585 },
1108  { -0.0020, -5.00, 0.0600 },
1109  { -0.0020, -5.10, 0.0620 },
1110  { -0.0025, -5.20, 0.0635 },
1111  { -0.0025, -5.40, 0.0655 },
1112  { -0.0025, -5.50, 0.0675 },
1113  { -0.0025, -5.60, 0.0690 },
1114  { -0.0030, -5.80, 0.0710 },
1115  { -0.0030, -5.90, 0.0720 },    /* 40 */
1116  { -0.0030, -6.00, 0.0740 },
1117  { -0.0035, -6.10, 0.0760 },
1118  { -0.0035, -6.30, 0.0790 },
1119  { -0.0040, -6.40, 0.0805 },
1120  { -0.0040, -6.50, 0.0820 },
1121  { -0.0040, -6.60, 0.0835 },
1122  { -0.0045, -6.80, 0.0855 },
1123  { -0.0045, -6.90, 0.0870 },
1124  { -0.0050, -7.00, 0.0885 },
1125  { -0.0050, -7.20, 0.0900 },    /* 50 */
1126  { -0.0050, -7.20, 0.0915 },
1127  { -0.0055, -7.40, 0.0930 },
1128  { -0.0055, -7.50, 0.0945 },
1129  { -0.0060, -7.60, 0.0960 },
1130  { -0.0060, -7.70, 0.0970 },
1131  { -0.0065, -7.80, 0.0985 },
1132  { -0.0060, -7.70, 0.0995 },
1133  { -0.0060, -7.60, 0.1010 },
1134  { -0.0060, -7.50, 0.1020 },
1135  { -0.0055, -7.30, 0.1030 },    /* 60 */
1136  { -0.0050, -7.10, 0.1040 },
1137  { -0.0050, -6.90, 0.1050 },
1138  { -0.0045, -6.80, 0.1065 },
1139  { -0.0045, -6.50, 0.1075 },
1140  { -0.0040, -6.40, 0.1085 },
1141  { -0.0040, -6.20, 0.1095 },
1142  { -0.0040, -6.00, 0.1105 },
1143  { -0.0035, -5.80, 0.1115 },
1144  { -0.0030, -5.50, 0.1125 },
1145  { -0.0030, -5.40, 0.1135 },    /* 70 */
1146  { -0.0030, -5.10, 0.1145 },
1147  { -0.0030, -4.90, 0.1150 },
1148  { -0.0025, -4.70, 0.1160 },
1149  { -0.0025, -4.40, 0.1165 },
1150  { -0.0025, -4.20, 0.1175 },
1151  { -0.0020, -3.90, 0.1180 },
1152  { -0.0020, -3.70, 0.1185 },
1153  { -0.0020, -3.40, 0.1190 },
1154  { -0.0020, -3.10, 0.1195 },
1155  { -0.0020, -2.90, 0.1200 },    /* 80 */
1156  { -0.0015, -2.60, 0.1200 },
1157  { -0.0015, -2.30, 0.1205 },
1158  { -0.0015, -2.00, 0.1210 },
1159  { -0.0015, -1.80, 0.1215 },
1160  { -0.0015, -1.50, 0.1215 },
1161  { -0.0015, -1.20, 0.1215 },
1162  { -0.0015, -0.90, 0.1215 },
1163  { -0.0015, -0.60, 0.1215 },
1164  { -0.0015, -0.30, 0.1215 },
1165  { -0.0010,  0.00, 0.1215 },    /* 90 */
1166  { -0.0010,  0.30, 0.1215 },
1167  { -0.0010,  0.60, 0.1215 },
1168  { -0.0010,  0.90, 0.1215 },
1169  { -0.0010,  1.20, 0.1215 },
1170  { -0.0015,  1.40, 0.1215 },
1171  { -0.0015,  1.70, 0.1215 },
1172  { -0.0015,  2.00, 0.1215 },
1173  { -0.0015,  2.30, 0.1215 },
1174  { -0.0015,  2.60, 0.1215 },
1175  { -0.0015,  2.80, 0.1220 },    /* 100 */
1176  { -0.0020,  3.10, 0.1225 },
1177  { -0.0020,  3.30, 0.1230 },
1178  { -0.0020,  3.60, 0.1235 },
1179  { -0.0020,  3.90, 0.1240 },
1180  { -0.0025,  4.10, 0.1245 },
1181  { -0.0025,  4.40, 0.1250 },
1182  { -0.0025,  4.60, 0.1260 },
1183  { -0.0025,  4.90, 0.1265 },
1184  { -0.0030,  5.10, 0.1275 },
1185  { -0.0030,  5.30, 0.1285 },    /* 110 */
1186  { -0.0035,  5.60, 0.1290 },
1187  { -0.0035,  5.80, 0.1300 },
1188  { -0.0035,  6.00, 0.1310 },
1189  { -0.0040,  6.20, 0.1325 },
1190  { -0.0040,  6.40, 0.1335 },
1191  { -0.0045,  6.60, 0.1345 },
1192  { -0.0045,  6.70, 0.1355 },
1193  { -0.0050,  6.90, 0.1365 },
1194  { -0.0050,  7.10, 0.1375 },
1195  { -0.0055,  7.30, 0.1390 },    /* 120 */
1196  { -0.0055,  7.40, 0.1400 },
1197  { -0.0060,  7.50, 0.1415 },
1198  { -0.0065,  8.00, 0.1425 },
1199  { -0.0065,  7.80, 0.1440 },
1200  { -0.0060,  7.80, 0.1455 },
1201  { -0.0060,  7.60, 0.1470 },
1202  { -0.0055,  7.50, 0.1485 },
1203  { -0.0055,  7.40, 0.1500 },
1204  { -0.0050,  7.30, 0.1515 },
1205  { -0.0050,  7.20, 0.1530 },    /* 130 */
1206  { -0.0050,  7.00, 0.1545 },
1207  { -0.0045,  6.90, 0.1560 },
1208  { -0.0045,  6.80, 0.1575 },
1209  { -0.0040,  6.70, 0.1590 },
1210  { -0.0040,  6.50, 0.1605 },
1211  { -0.0040,  6.40, 0.1625 },
1212  { -0.0035,  6.30, 0.1640 },
1213  { -0.0035,  6.10, 0.1655 },
1214  { -0.0030,  6.10, 0.1670 },
1215  { -0.0030,  5.90, 0.1690 },    /* 140 */
1216  { -0.0030,  5.70, 0.1705 },
1217  { -0.0025,  5.70, 0.1720 },
1218  { -0.0025,  5.50, 0.1740 },
1219  { -0.0025,  5.40, 0.1755 },
1220  { -0.0025,  5.20, 0.1775 },
1221  { -0.0020,  5.10, 0.1790 },
1222  { -0.0020,  5.00, 0.1810 },
1223  { -0.0020,  4.80, 0.1825 },
1224  { -0.0015,  4.70, 0.1840 },
1225  { -0.0015,  4.60, 0.1860 },    /* 150 */
1226  { -0.0015,  4.40, 0.1880 },
1227  { -0.0015,  4.20, 0.1900 },
1228  { -0.0015,  4.10, 0.1915 },
1229  { -0.0010,  4.00, 0.1935 },
1230  { -0.0010,  3.80, 0.1955 },
1231  { -0.0010,  3.70, 0.1970 },
1232  { -0.0010,  3.50, 0.1990 },
1233  { -0.0005,  3.40, 0.2010 },
1234  { -0.0010,  3.20, 0.2025 },
1235  { -0.0005,  3.10, 0.2045 },    /* 160 */
1236  { -0.0005,  2.90, 0.2065 },
1237  { -0.0005,  2.80, 0.2085 },
1238  { -0.0005,  2.60, 0.2105 },
1239  { -0.0005,  2.50, 0.2120 },
1240  { -0.0005,  2.30, 0.2140 },
1241  { -0.0005,  2.20, 0.2160 },
1242  { -0.0005,  2.00, 0.2180 },
1243  {  0.0000,  1.90, 0.2200 },
1244  {  0.0000,  1.70, 0.2220 },
1245  {  0.0000,  1.60, 0.2235 },    /* 170 */
1246  {  0.0000,  1.40, 0.2255 },
1247  {  0.0000,  1.30, 0.2275 },
1248  {  0.0000,  1.10, 0.2295 },
1249  {  0.0000,  0.90, 0.2315 },
1250  {  0.0000,  0.80, 0.2335 },
1251  {  0.0000,  0.60, 0.2355 },
1252  {  0.0000,  0.50, 0.2375 },
1253  {  0.0000,  0.30, 0.2395 },
1254  {  0.0000,  0.10, 0.2415 },
1255  {  0.0000,  0.00, 0.2430 },    /* 180 */
1256  {  0.0000, -0.10, 0.2450 },
1257  {  0.0000, -0.30, 0.2470 },
1258  {  0.0000, -0.40, 0.2490 },
1259  {  0.0000, -0.60, 0.2510 },
1260  {  0.0000, -0.80, 0.2530 },
1261  {  0.0000, -0.90, 0.2550 },
1262  {  0.0000, -1.10, 0.2570 },
1263  {  0.0000, -1.20, 0.2590 },
1264  {  0.0000, -1.40, 0.2610 },
1265  {  0.0000, -1.50, 0.2625 },    /* 190 */
1266  {  0.0000, -1.70, 0.2645 },
1267  {  0.0000, -1.80, 0.2665 },
1268  { -0.0005, -2.00, 0.2685 },
1269  { -0.0005, -2.10, 0.2705 },
1270  { -0.0005, -2.30, 0.2725 },
1271  { -0.0005, -2.40, 0.2740 },
1272  { -0.0005, -2.60, 0.2760 },
1273  { -0.0005, -2.80, 0.2780 },
1274  { -0.0005, -2.90, 0.2800 },
1275  { -0.0005, -3.00, 0.2820 },    /* 200 */
1276  { -0.0010, -3.20, 0.2835 },
1277  { -0.0005, -3.30, 0.2855 },
1278  { -0.0010, -3.50, 0.2875 },
1279  { -0.0010, -3.70, 0.2895 },
1280  { -0.0010, -3.80, 0.2910 },
1281  { -0.0010, -3.90, 0.2930 },
1282  { -0.0010, -4.00, 0.2950 },
1283  { -0.0015, -4.20, 0.2965 },
1284  { -0.0015, -4.40, 0.2985 },
1285  { -0.0015, -4.50, 0.3000 },    /* 210 */
1286  { -0.0015, -4.60, 0.3020 },
1287  { -0.0020, -4.80, 0.3040 },
1288  { -0.0020, -5.00, 0.3055 },
1289  { -0.0020, -5.00, 0.3075 },
1290  { -0.0025, -5.20, 0.3090 },
1291  { -0.0025, -5.30, 0.3110 },
1292  { -0.0025, -5.50, 0.3125 },
1293  { -0.0025, -5.60, 0.3140 },
1294  { -0.0030, -5.70, 0.3160 },
1295  { -0.0030, -5.90, 0.3175 },    /* 220 */
1296  { -0.0030, -6.00, 0.3190 },
1297  { -0.0035, -6.10, 0.3210 },
1298  { -0.0035, -6.30, 0.3225 },
1299  { -0.0040, -6.40, 0.3240 },
1300  { -0.0040, -6.50, 0.3255 },
1301  { -0.0040, -6.60, 0.3270 },
1302  { -0.0045, -6.80, 0.3290 },
1303  { -0.0045, -6.90, 0.3305 },
1304  { -0.0050, -7.00, 0.3320 },
1305  { -0.0050, -7.20, 0.3335 },    /* 230 */
1306  { -0.0050, -7.20, 0.3350 },
1307  { -0.0055, -7.40, 0.3365 },
1308  { -0.0055, -7.50, 0.3380 },
1309  { -0.0060, -7.60, 0.3390 },
1310  { -0.0060, -7.70, 0.3405 },
1311  { -0.0065, -7.80, 0.3420 },
1312  { -0.0060, -7.60, 0.3425 },
1313  { -0.0060, -7.50, 0.3440 },
1314  { -0.0055, -7.40, 0.3455 },
1315  { -0.0055, -7.20, 0.3470 },    /* 240 */
1316  { -0.0050, -7.10, 0.3480 },
1317  { -0.0050, -6.90, 0.3490 },
1318  { -0.0045, -6.80, 0.3500 },
1319  { -0.0045, -6.50, 0.3510 },
1320  { -0.0040, -6.40, 0.3520 },
1321  { -0.0040, -6.10, 0.3535 },
1322  { -0.0035, -6.00, 0.3545 },
1323  { -0.0035, -5.80, 0.3550 },
1324  { -0.0030, -5.50, 0.3560 },
1325  { -0.0030, -5.30, 0.3570 },    /* 250 */
1326  { -0.0030, -5.10, 0.3580 },
1327  { -0.0030, -4.90, 0.3585 },
1328  { -0.0025, -4.70, 0.3595 },
1329  { -0.0025, -4.40, 0.3600 },
1330  { -0.0020, -4.10, 0.3610 },
1331  { -0.0020, -3.90, 0.3615 },
1332  { -0.0020, -3.70, 0.3620 },
1333  { -0.0020, -3.30, 0.3625 },
1334  { -0.0020, -3.10, 0.3630 },
1335  { -0.0015, -2.80, 0.3635 },    /* 260 */
1336  { -0.0015, -2.60, 0.3640 },
1337  { -0.0015, -2.40, 0.3645 },
1338  { -0.0015, -2.00, 0.3645 },
1339  { -0.0015, -1.80, 0.3650 },
1340  { -0.0015, -1.40, 0.3650 },
1341  { -0.0015, -1.20, 0.3655 },
1342  { -0.0010, -0.90, 0.3655 },
1343  { -0.0010, -0.60, 0.3655 },
1344  { -0.0010, -0.30, 0.3655 },
1345  { -0.0010,  0.00, 0.3655 },    /* 270 */
1346  { -0.0010,  0.30, 0.3655 },
1347  { -0.0010,  0.60, 0.3655 },
1348  { -0.0010,  0.90, 0.3655 },
1349  { -0.0015,  1.10, 0.3655 },
1350  { -0.0015,  1.40, 0.3655 },
1351  { -0.0015,  1.70, 0.3655 },
1352  { -0.0015,  1.90, 0.3660 },
1353  { -0.0015,  2.20, 0.3660 },
1354  { -0.0015,  2.50, 0.3665 },
1355  { -0.0015,  2.80, 0.3670 },    /* 280 */
1356  { -0.0015,  3.10, 0.3675 },
1357  { -0.0020,  3.40, 0.3680 },
1358  { -0.0020,  3.70, 0.3685 },
1359  { -0.0020,  3.90, 0.3690 },
1360  { -0.0025,  4.10, 0.3695 },
1361  { -0.0025,  4.40, 0.3700 },
1362  { -0.0025,  4.60, 0.3710 },
1363  { -0.0025,  4.80, 0.3715 },
1364  { -0.0025,  5.00, 0.3730 },
1365  { -0.0030,  5.40, 0.3735 },    /* 290 */
1366  { -0.0035,  5.60, 0.3745 },
1367  { -0.0035,  5.80, 0.3755 },
1368  { -0.0035,  6.00, 0.3765 },
1369  { -0.0040,  6.20, 0.3775 },
1370  { -0.0045,  6.50, 0.3785 },
1371  { -0.0045,  6.60, 0.3795 },
1372  { -0.0045,  6.80, 0.3805 },
1373  { -0.0050,  7.00, 0.3815 },
1374  { -0.0050,  7.10, 0.3825 },
1375  { -0.0055,  7.20, 0.3840 },    /* 300 */
1376  { -0.0055,  7.40, 0.3850 },
1377  { -0.0060,  7.50, 0.3865 },
1378  { -0.0060,  7.70, 0.3875 },
1379  { -0.0065,  7.80, 0.3890 },
1380  { -0.0060,  7.80, 0.3900 },
1381  { -0.0060,  7.60, 0.3915 },
1382  { -0.0055,  7.60, 0.3930 },
1383  { -0.0055,  7.40, 0.3945 },
1384  { -0.0050,  7.30, 0.3960 },
1385  { -0.0050,  7.20, 0.3975 },    /* 310 */
1386  { -0.0050,  7.00, 0.3990 },
1387  { -0.0045,  6.90, 0.4005 },
1388  { -0.0045,  6.80, 0.4020 },
1389  { -0.0040,  6.70, 0.4035 },
1390  { -0.0040,  6.60, 0.4050 },
1391  { -0.0040,  6.40, 0.4065 },
1392  { -0.0035,  6.30, 0.4085 },
1393  { -0.0035,  6.20, 0.4100 },
1394  { -0.0030,  6.10, 0.4115 },
1395  { -0.0030,  5.90, 0.4130 },    /* 320 */
1396  { -0.0030,  5.80, 0.4150 },
1397  { -0.0025,  5.70, 0.4165 },
1398  { -0.0025,  5.50, 0.4180 },
1399  { -0.0025,  5.40, 0.4200 },
1400  { -0.0025,  5.20, 0.4215 },
1401  { -0.0020,  5.10, 0.4235 },
1402  { -0.0020,  5.00, 0.4250 },
1403  { -0.0020,  4.80, 0.4270 },
1404  { -0.0015,  4.70, 0.4285 },
1405  { -0.0015,  4.60, 0.4305 },    /* 330 */
1406  { -0.0015,  4.40, 0.4325 },
1407  { -0.0015,  4.20, 0.4340 },
1408  { -0.0015,  4.10, 0.4360 },
1409  { -0.0010,  4.00, 0.4375 },
1410  { -0.0010,  3.80, 0.4395 },
1411  { -0.0010,  3.70, 0.4415 },
1412  { -0.0010,  3.50, 0.4435 },
1413  { -0.0005,  3.40, 0.4450 },
1414  { -0.0010,  3.20, 0.4470 },
1415  { -0.0005,  3.10, 0.4490 },    /* 340 */
1416  { -0.0005,  2.90, 0.4510 },
1417  { -0.0005,  2.80, 0.4525 },
1418  { -0.0005,  2.60, 0.4545 },
1419  { -0.0005,  2.40, 0.4565 },
1420  { -0.0005,  2.30, 0.4585 },
1421  { -0.0005,  2.20, 0.4605 },
1422  { -0.0005,  2.00, 0.4620 },
1423  {  0.0000,  1.90, 0.4640 },
1424  {  0.0000,  1.70, 0.4660 },
1425  {  0.0000,  1.60, 0.4680 },    /* 350 */
1426  {  0.0000,  1.40, 0.4700 },
1427  {  0.0000,  1.20, 0.4720 },
1428  {  0.0000,  1.10, 0.4740 },
1429  {  0.0000,  0.90, 0.4760 },
1430  {  0.0000,  0.80, 0.4780 },
1431  {  0.0000,  0.60, 0.4795 },
1432  {  0.0000,  0.50, 0.4815 },
1433  {  0.0000,  0.30, 0.4835 },
1434  {  0.0000,  0.20, 0.4855 },    /* 359 */
1435 };
1436
1437
1438 /* Turn the crank by 1 degree, which moves the legs and displaces the robot.
1439  */
1440 static void
1441 tick_walker (ModeInfo *mi, walker *f)
1442 {
1443   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1444   int deg;
1445   GLfloat th, fwd;
1446
1447   if (bp->button_down_p) return;
1448
1449   f->crank_rot++;
1450   deg = ((int) (f->crank_rot + 0.5)) % 360;
1451
1452 # ifdef DEBUG
1453   if (debug_p)
1454     {
1455       f->crank_rot = bp->debug_x;
1456       f->pitch = wobble_profile[deg].rot;
1457       f->z     = wobble_profile[deg].up;
1458     }
1459   else
1460 # endif /* DEBUG */
1461
1462   if (deg == 0)
1463     {
1464       fwd      = wobble_profile[deg].fwd;
1465       f->pitch = wobble_profile[deg].rot;
1466       f->z     = wobble_profile[deg].up;
1467     }
1468   else
1469     {
1470       fwd       = (wobble_profile[deg].fwd - wobble_profile[deg-1].fwd);
1471       f->pitch += (wobble_profile[deg].rot - wobble_profile[deg-1].rot);
1472       f->z     += (wobble_profile[deg].up  - wobble_profile[deg-1].up);
1473     }
1474
1475   /* Lean slightly toward the foot that is raised off the ground. */
1476   f->roll = -2.5 * sin ((deg - 90) * M_PI / 180);
1477
1478   if (!(random() % 10))
1479     {
1480       GLfloat b = f->balance / 10.0;
1481       int s = (f->balance > 0 ? 1 : -1);
1482       if (s < 0) b = -b;
1483       f->facing += s * frand (b);
1484     }
1485
1486 # ifdef DEBUG
1487   if (debug_p) fwd = 0;
1488 # endif
1489
1490   {
1491     GLfloat ox = f->x;
1492     GLfloat oy = f->y;
1493     th = f->facing * M_PI / 180.0;
1494     f->x += fwd * cos (th);
1495     f->y += fwd * sin (th);
1496
1497     /* If moving this robot would collide with another, undo the move,
1498        recoil, and randomly turn.
1499      */
1500     if (collision_p (mi, f, 0))
1501       {
1502         fwd *= -1.5;
1503         f->x = ox + fwd * cos (th);
1504         f->y = oy + fwd * sin (th);
1505         f->facing += frand(10) - 5;
1506         if (! random() % 30)
1507           f->facing += frand(90) - 45;
1508       }
1509   }
1510
1511
1512   if (!do_fade ||
1513       opacity > 0.5)   /* Don't bother fading if it's already transparent. */
1514     {
1515       GLfloat tick = 0.002;
1516       GLfloat linger = 3;
1517
1518       /* If we're not fading, maybe start fading out. */
1519       if (f->fading_p == 0 && ! (random() % 40000))
1520         f->fading_p = -1;
1521
1522 # ifdef DEBUG
1523       if (debug_p) f->fading_p = 0;
1524 # endif
1525
1526       if (f->fading_p < 0)
1527         {
1528           f->body_transparency -= tick;
1529           if (f->body_transparency <= -linger)
1530             {
1531               f->body_transparency = -linger;
1532               f->fading_p = 1;
1533             }
1534         }
1535       else if (f->fading_p > 0)
1536         {
1537           f->body_transparency += tick;
1538           if (f->body_transparency >= opacity)
1539             {
1540               f->body_transparency = opacity;
1541               f->fading_p = 0;
1542             }
1543         }
1544     }
1545 }
1546
1547
1548 static void
1549 init_walker (ModeInfo *mi, walker *f)
1550 {
1551   int i, start_tick = random() % 360;
1552
1553   f->crank_rot = 0;
1554   f->pitch = wobble_profile[0].rot;
1555   f->z     = wobble_profile[0].up;
1556
1557   f->body_transparency = opacity;
1558
1559   f->hand_rot[0] = frand(180);
1560   f->hand_pos[0] = 0.6 + frand(0.4);
1561   f->hand_rot[1] = 180 - f->hand_rot[0];
1562   f->hand_pos[1] = f->hand_pos[0];
1563
1564   if (! (random() % 30)) f->hand_rot[1] += frand(10);
1565   if (! (random() % 30)) f->hand_pos[1] = 0.6 + frand(0.4);
1566
1567   f->facing = frand(360);
1568   f->balance = frand(10) - 5;
1569
1570   if (MI_COUNT(mi) == 1)
1571     f->speed = 1.0;
1572   else
1573     f->speed = 0.6 + frand(0.8);
1574
1575 # ifdef DEBUG
1576   if (debug_p)
1577     {
1578       start_tick = 0;
1579       f->facing = 0;
1580       f->balance = 0;
1581     }
1582 # endif
1583
1584   for (i = 0; i < start_tick; i++)
1585     tick_walker (mi, f);
1586
1587   /* Place them randomly, but non-overlapping. */
1588   for (i = 0; i < 1000; i++)
1589     {
1590       GLfloat range = 10;
1591       if (MI_COUNT(mi) > 10) range += MI_COUNT(mi) / 10.0;
1592       f->x = frand(range) - range/2;
1593       f->y = frand(range) - range/2;
1594       if (! collision_p (mi, f, 1.5))
1595         break;
1596     }
1597
1598 # ifdef DEBUG
1599   if (debug_p) f->x = f->y = 0;
1600 # endif
1601
1602 }
1603
1604
1605 /* Draw a robot standing in the right place, 1 unit tall.
1606  */
1607 static int
1608 draw_walker (ModeInfo *mi, walker *f, const char *tag)
1609 {
1610   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1611   int wire = MI_IS_WIREFRAME(mi);
1612   int count = 0;
1613   glPushMatrix();
1614
1615   glTranslatef (f->y, f->z, f->x);
1616
1617   {
1618     GLfloat n = 0.01;
1619     glScalef (n, n, n);
1620   }
1621
1622   glRotatef (90, 0, 1, 0);
1623   glRotatef (f->facing, 0, 1, 0);
1624   glRotatef (f->pitch,  0, 0, 1);
1625   glRotatef (f->roll,   1, 0, 0);
1626
1627   {
1628     GLfloat n = 0.00484;           /* make it 1 unit tall */
1629     glScalef (n, n, n);
1630   }
1631
1632   count += draw_gearbox (mi);
1633   count += draw_crank (mi, f, f->crank_rot);
1634   count += draw_rotator (mi, f, f->crank_rot);
1635   count += draw_leg (mi, f->crank_rot, False);
1636   count += draw_leg (mi, f->crank_rot, True);
1637   count += draw_wireframe (mi, f);
1638
1639   /* Draw these last, and outer shell first, to make transparency work.
1640      The order in which things hit the depth buffer matters.
1641    */
1642   if (f->body_transparency >= 0.001)
1643     {
1644       count += draw_arm (mi, f, True,  f->hand_rot[0], f->hand_pos[0]);
1645       count += draw_arm (mi, f, False, f->hand_rot[1], f->hand_pos[1]);
1646       count += draw_body (mi, f, False);
1647       count += draw_body (mi, f, True);
1648       count += draw_dome (mi, f);
1649     }
1650
1651   if (tag)  /* For debugging depth sorting: label each robot */
1652     {
1653       GLfloat m[4][4];
1654       if (! wire) glDisable (GL_DEPTH_TEST);
1655       glColor3f (1, 1, 1);
1656       glPushMatrix();
1657
1658       /* Billboard rotation */
1659       glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
1660       m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
1661       m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
1662       m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
1663       glLoadIdentity();
1664       glMultMatrixf (&m[0][0]);
1665       glScalef (0.04, 0.04, 0.04);
1666
1667       print_texture_string (bp->font_data, tag);
1668       glPopMatrix();
1669       if (! wire) glEnable (GL_DEPTH_TEST);
1670     }
1671
1672   glPopMatrix();
1673   return count;
1674 }
1675
1676
1677 static int
1678 draw_ground (ModeInfo *mi, GLfloat color[4])
1679 {
1680   int wire = MI_IS_WIREFRAME(mi);
1681   GLfloat i;
1682   GLfloat cell_size = 0.9;
1683   int cells = 1000 * size;
1684   int points = 0;
1685
1686 # ifdef DEBUG
1687   if (debug_p) return 0;
1688 # endif
1689
1690   glPushMatrix();
1691
1692   glRotatef (frand(90), 0, 0, 1);
1693
1694   if (!wire)
1695     {
1696       GLfloat fog_color[4] = { 0, 0, 0, 1 };
1697
1698       glLineWidth (2);
1699       glEnable (GL_LINE_SMOOTH);
1700       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
1701       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
1702       glEnable (GL_BLEND);
1703
1704       glFogi (GL_FOG_MODE, GL_EXP2);
1705       glFogfv (GL_FOG_COLOR, fog_color);
1706       glFogf (GL_FOG_DENSITY, 0.017);
1707       glFogf (GL_FOG_START, -cells/2 * cell_size);
1708 # ifndef USE_IPHONE  /* #### Not working on iOS for some reason */
1709       glEnable (GL_FOG);
1710 # endif
1711     }
1712
1713   glColor4fv (color);
1714   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
1715
1716   glBegin (GL_LINES);
1717   for (i = -cells/2; i < cells/2; i++)
1718     {
1719       GLfloat a = i * cell_size;
1720       GLfloat b = cells/2 * cell_size;
1721       glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++;
1722       glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++;
1723     }
1724   glEnd();
1725
1726   if (!wire)
1727     {
1728       glDisable (GL_LINE_SMOOTH);
1729       glDisable (GL_BLEND);
1730       glDisable (GL_FOG);
1731     }
1732
1733   glPopMatrix();
1734
1735   return points;
1736 }
1737
1738
1739 /* If the target robot (robot #0) has moved too far from the point at which
1740    the camera is aimed, then initiate an animation to move the observer to
1741    the new spot.
1742
1743    Because of the jerky forward motion of the robots, just always focusing
1744    on the center of the robot looks terrible, so instead we let them walk
1745    a little out of the center of the frame, and then catch up.
1746  */
1747 static void
1748 look_at_center (ModeInfo *mi)
1749 {
1750   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1751   GLfloat target_x = bp->walkers[0].x;
1752   GLfloat target_y = bp->walkers[0].y;
1753   GLfloat target_z = 0.8;   /* Look a little bit above his head */
1754   GLfloat max_dist = 2.5 / size;
1755
1756 # ifdef DEBUG
1757   if (debug_p) return;
1758 # endif
1759
1760   if (max_dist < 1)  max_dist = 1;
1761   if (max_dist > 10) max_dist = 10;
1762
1763   if (bp->camera_tracking_p)
1764     {
1765       GLfloat r = (1 - cos (bp->tracking_ratio * M_PI)) / 2;
1766       bp->looking_x = bp->olooking_x + r * (target_x - bp->olooking_x);
1767       bp->looking_y = bp->olooking_y + r * (target_y - bp->olooking_y);
1768       bp->looking_z = bp->olooking_z + r * (target_z - bp->olooking_z);
1769
1770       bp->tracking_ratio += 0.02;
1771       if (bp->tracking_ratio >= 1)
1772         {
1773           bp->camera_tracking_p = False;
1774           bp->olooking_x = bp->looking_x;
1775           bp->olooking_y = bp->looking_y;
1776           bp->olooking_z = bp->looking_z;
1777         }
1778     }
1779           
1780   if (! bp->camera_tracking_p)
1781     {
1782       GLfloat dist =
1783         sqrt ((target_x - bp->looking_x) * (target_x - bp->looking_x) +
1784               (target_y - bp->looking_y) * (target_y - bp->looking_y) +
1785               (target_z - bp->looking_z) * (target_z - bp->looking_z));
1786
1787       if (dist > max_dist)
1788         {
1789           bp->camera_tracking_p = True;
1790           bp->tracking_ratio = 0;
1791           bp->olooking_x = bp->looking_x;
1792           bp->olooking_y = bp->looking_y;
1793           bp->olooking_z = bp->looking_z;
1794         }
1795     }
1796
1797   glTranslatef (-bp->looking_y, -bp->looking_z, -bp->looking_x);
1798
1799 # if 0 /* DEBUG */
1800   {
1801     GLfloat th;
1802     glPushMatrix();
1803     glColor3f(1, 0, 0);
1804     glTranslatef (target_y, target_z, target_x);
1805     glBegin(GL_LINES);
1806     glVertex3f(0, -target_z, 0);
1807     glVertex3f(0, 1, 0);
1808     glVertex3f(-0.1, 0, -0.1);
1809     glVertex3f( 0.1, 0,  0.1);
1810     glVertex3f(-0.1, 0,  0.1);
1811     glVertex3f( 0.1, 0, -0.1);
1812     glEnd();
1813     glPopMatrix();
1814
1815     glPushMatrix();
1816     glColor3f(0, 1, 0);
1817     glTranslatef (bp->looking_y, bp->looking_z, bp->looking_x);
1818     glRotatef (30, 0, 1, 0);
1819     glBegin(GL_LINES);
1820     glVertex3f(0, -bp->looking_z, 0);
1821     glVertex3f(0, 1, 0);
1822     glVertex3f(-0.1, 0, -0.1);
1823     glVertex3f( 0.1, 0,  0.1);
1824     glVertex3f(-0.1, 0,  0.1);
1825     glVertex3f( 0.1, 0, -0.1);
1826     glEnd();
1827     glPopMatrix();
1828
1829     glPushMatrix();
1830     glColor3f(0, 0, 1);
1831     glTranslatef (bp->olooking_y, bp->olooking_z, bp->olooking_x);
1832     glRotatef (60, 0, 1, 0);
1833     glBegin(GL_LINES);
1834     glVertex3f(0, -bp->olooking_z, 0);
1835     glVertex3f(0, 1, 0);
1836     glVertex3f(-0.1, 0, -0.1);
1837     glVertex3f( 0.1, 0,  0.1);
1838     glVertex3f(-0.1, 0,  0.1);
1839     glVertex3f( 0.1, 0, -0.1);
1840     glEnd();
1841
1842     glTranslatef (0, -bp->olooking_z, 0);
1843     glBegin (GL_LINE_LOOP);
1844     for (th = 0; th < M_PI * 2; th += 0.1)
1845       glVertex3f (bp->olooking_y + max_dist * cos(th), 0,
1846                   bp->olooking_x + max_dist * sin(th));
1847     glEnd();
1848     glPopMatrix();
1849   }
1850 # endif /* DEBUG */
1851 }
1852
1853
1854 #ifdef WORDBUBBLES
1855
1856 /* Draw a cartoony word bubble.
1857    W and H are the inside size, for text.
1858    Origin is at bottom left.
1859    The bubble frame and arrow are outside that.
1860  */
1861 static void
1862 draw_bubble_box (ModeInfo *mi,
1863                  GLfloat width, GLfloat height,
1864                  GLfloat corner_radius,
1865                  GLfloat arrow_h, GLfloat arrow_x,
1866                  GLfloat fg[4], GLfloat bg[4])
1867 {
1868
1869 # define CORNER_POINTS 16
1870   GLfloat outline_points[ (CORNER_POINTS + 2) * 4 + 8 ][3];
1871   int i = 0;
1872   GLfloat th;
1873   GLfloat tick = M_PI / 2 / CORNER_POINTS;
1874
1875   GLfloat arrow_w = arrow_h / 2;
1876   GLfloat arrow_x2 = MAX(0, MIN(width - arrow_w, arrow_x));
1877
1878   GLfloat w2 = MAX(arrow_w, width  - corner_radius * 1.10);
1879   GLfloat h2 = MAX(0,       height - corner_radius * 1.28);
1880   GLfloat x2 = (width  - w2) / 2;
1881   GLfloat y2 = (height - h2) / 2;
1882                                         /*        A  B         C   D    */
1883   GLfloat xa = x2 -corner_radius;       /*    E     _------------_      */
1884   GLfloat xb = x2;                      /*    D   /__|         |__\     */
1885   GLfloat xc = xb + w2;                 /*        |  |         |  |     */
1886   GLfloat xd = xc + corner_radius;      /*    C   |__|   EF    |__|     */
1887   GLfloat xe = xb + arrow_x2;           /*    B    \_|_________|_/      */
1888   GLfloat xf = xe + arrow_w;            /*    A          \|             */
1889
1890   GLfloat ya = y2 - (corner_radius + arrow_h);
1891   GLfloat yb = y2 - corner_radius;
1892   GLfloat yc = y2;
1893   GLfloat yd = yc + h2;
1894   GLfloat ye = yd + corner_radius;
1895
1896   GLfloat z = 0;
1897
1898   /* Let the lines take precedence over the fills. */
1899   glEnable (GL_POLYGON_OFFSET_FILL);
1900   glPolygonOffset (1.0, 1.0);
1901
1902   glColor4fv (bg);
1903   glFrontFace(GL_CW);
1904
1905   /* top left corner */
1906
1907   glBegin (GL_TRIANGLE_FAN);
1908   glVertex3f (xb, yd, 0);
1909   for (th = 0; th < M_PI/2 + tick; th += tick)
1910     {
1911       GLfloat x = xb - corner_radius * cos(th);
1912       GLfloat y = yd + corner_radius * sin(th);
1913       glVertex3f (x, y, z);
1914       outline_points[i][0] = x;
1915       outline_points[i][1] = y;
1916       outline_points[i][2] = z;
1917       i++;
1918     }
1919   glEnd();
1920
1921   /* top edge */
1922   outline_points[i][0] = xc;
1923   outline_points[i][1] = ye;
1924   outline_points[i][2] = z;
1925   i++;
1926
1927   /* top right corner */
1928
1929   glBegin (GL_TRIANGLE_FAN);
1930   glVertex3f (xc, yd, 0);
1931   for (th = M_PI/2; th > -tick; th -= tick)
1932     {
1933       GLfloat x = xc + corner_radius * cos(th);
1934       GLfloat y = yd + corner_radius * sin(th);
1935       glVertex3f (x, y, z);
1936       outline_points[i][0] = x;
1937       outline_points[i][1] = y;
1938       outline_points[i][2] = z;
1939       i++;
1940     }
1941   glEnd();
1942
1943   /* right edge */
1944   outline_points[i][0] = xd;
1945   outline_points[i][1] = yc;
1946   outline_points[i][2] = z;
1947   i++;
1948
1949   /* bottom right corner */
1950
1951   glBegin (GL_TRIANGLE_FAN);
1952   glVertex3f (xc, yc, 0);
1953   for (th = 0; th < M_PI/2 + tick; th += tick)
1954     {
1955       GLfloat x = xc + corner_radius * cos(th);
1956       GLfloat y = yc - corner_radius * sin(th);
1957       glVertex3f (x, y, z);
1958       outline_points[i][0] = x;
1959       outline_points[i][1] = y;
1960       outline_points[i][2] = z;
1961       i++;
1962     }
1963   glEnd();
1964
1965   /* bottom right edge */
1966   outline_points[i][0] = xf;
1967   outline_points[i][1] = yb;
1968   outline_points[i][2] = z;
1969   i++;
1970
1971   /* arrow triangle */
1972   glFrontFace(GL_CW);
1973   glBegin (GL_TRIANGLES);
1974
1975   /* bottom arrow point */
1976   outline_points[i][0] = xf;
1977   outline_points[i][1] = yb;
1978   outline_points[i][2] = z;
1979   glVertex3f (outline_points[i][0],
1980               outline_points[i][1],
1981               outline_points[i][2]);
1982   i++;
1983
1984   /* bottom right edge */
1985   outline_points[i][0] = xf;
1986   outline_points[i][1] = ya;
1987   outline_points[i][2] = z;
1988   glVertex3f (outline_points[i][0],
1989               outline_points[i][1],
1990               outline_points[i][2]);
1991   i++;
1992
1993   outline_points[i][0] = xe;
1994   outline_points[i][1] = yb;
1995   outline_points[i][2] = z;
1996   glVertex3f (outline_points[i][0],
1997               outline_points[i][1],
1998               outline_points[i][2]);
1999   i++;
2000   glEnd();
2001
2002
2003   /* bottom left corner */
2004
2005   glBegin (GL_TRIANGLE_FAN);
2006   glVertex3f (xb, yc, 0);
2007   for (th = M_PI/2; th > -tick; th -= tick)
2008     {
2009       GLfloat x = xb - corner_radius * cos(th);
2010       GLfloat y = yc - corner_radius * sin(th);
2011       glVertex3f (x, y, z);
2012       outline_points[i][0] = x;
2013       outline_points[i][1] = y;
2014       outline_points[i][2] = z;
2015       i++;
2016     }
2017   glEnd();
2018
2019   glFrontFace(GL_CCW);
2020
2021   /* left edge */
2022   outline_points[i][0] = xa;
2023   outline_points[i][1] = yd;
2024   outline_points[i][2] = z;
2025   i++;
2026
2027   glFrontFace(GL_CW);
2028   glBegin (GL_QUADS);
2029   /* left box */
2030   glVertex3f (xa, yd, z);
2031   glVertex3f (xb, yd, z);
2032   glVertex3f (xb, yc, z);
2033   glVertex3f (xa, yc, z);
2034
2035   /* center box */
2036   glVertex3f (xb, ye, z);
2037   glVertex3f (xc, ye, z);
2038   glVertex3f (xc, yb, z);
2039   glVertex3f (xb, yb, z);
2040
2041   /* right box */
2042   glVertex3f (xc, yd, z);
2043   glVertex3f (xd, yd, z);
2044   glVertex3f (xd, yc, z);
2045   glVertex3f (xc, yc, z);
2046
2047   glEnd();
2048
2049   glLineWidth (2.8);
2050   glColor4fv (fg);
2051
2052   glBegin (GL_LINE_LOOP);
2053   while (i > 0)
2054     glVertex3fv (outline_points[--i]);
2055   glEnd();
2056
2057   glDisable (GL_POLYGON_OFFSET_FILL);
2058 }
2059
2060
2061 static void
2062 draw_label (ModeInfo *mi, walker *f, GLfloat y_off, GLfloat scale,
2063             const char *label)
2064 {
2065   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2066   int wire = MI_IS_WIREFRAME(mi);
2067   GLfloat m[4][4];
2068
2069   if (scale == 0) return;
2070
2071   if (!wire)
2072     glDisable (GL_LIGHTING);   /* don't light fonts */
2073
2074   glPushMatrix();
2075
2076   /* First, we translate the origin to the center of the robot.
2077
2078      Then we retrieve the prevailing modelview matrix, which
2079      includes any rotation, wandering, and user-trackball-rolling
2080      of the scene.
2081
2082      We set the top 3x3 cells of that matrix to be the identity
2083      matrix.  This removes all rotation from the matrix, while
2084      leaving the translation alone.  This has the effect of
2085      leaving the prevailing coordinate system perpendicular to
2086      the camera view: were we to draw a square face, it would
2087      be in the plane of the screen.
2088
2089      Now we translate by `size' toward the viewer -- so that the
2090      origin is *just in front* of the ball.
2091
2092      Then we draw the label text, allowing the depth buffer to
2093      do its work: that way, labels on atoms will be occluded
2094      properly when other atoms move in front of them.
2095
2096      This technique (of neutralizing rotation relative to the
2097      observer, after both rotations and translations have been
2098      applied) is known as "billboarding".
2099    */
2100
2101   if (f)
2102     glTranslatef(f->y, 0, f->x);                   /* get matrix */
2103
2104   glTranslatef (0, y_off, 0);
2105
2106   glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
2107   m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
2108   m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
2109   m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
2110   glLoadIdentity();                             /* reset modelview */
2111   glMultMatrixf (&m[0][0]);                     /* replace with ours */
2112
2113   glTranslatef (0, 0, 0.1);                     /* move toward camera */
2114
2115   glRotatef (current_device_rotation(), 0, 0, 1);  /* right side up */
2116
2117   {
2118     int cw, ch, w, h;
2119     GLfloat s;
2120     GLfloat max = 24;   /* max point size to avoid pixellated text */
2121
2122     /* Let the font be much larger on iPhone */
2123     if (mi->xgwa.height <= 640 || mi->xgwa.width <= 640)
2124       max *= 3;
2125     
2126     cw = texture_string_width (bp->font_data, "X", &ch);  /* line height */
2127     s = 1.0 / ch;
2128     if (ch > max) s *= max/ch;
2129
2130     s *= scale;
2131
2132     w = texture_string_width (bp->font_data, label, &h);
2133
2134     glScalef (s, s, 1);
2135     glTranslatef (-w/2, h*2/3 + (cw * 7), 0);
2136
2137     glPushMatrix();
2138     glTranslatef (0, -h + (ch * 1.2), -0.1);
2139     draw_bubble_box (mi, w, h, 
2140                      ch * 2,            /* corner radius */
2141                      ch * 2.5,          /* arrow height */
2142                      w / 2 - cw * 8,    /* arrow x */
2143                      bp->text_bd, bp->text_bg);
2144     glPopMatrix();
2145
2146     glColor4fv (bp->text_color);
2147     print_texture_string (bp->font_data, label);
2148   }
2149
2150   glPopMatrix();
2151
2152   /* More efficient to always call glEnable() with correct values
2153      than to call glPushAttrib()/glPopAttrib(), since reading
2154      attributes from GL does a round-trip and  stalls the pipeline.
2155    */
2156   if (!wire)
2157     glEnable (GL_LIGHTING);
2158 }
2159
2160
2161 static void
2162 fill_words (ModeInfo *mi)
2163 {
2164   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2165   char *p = bp->words + strlen(bp->words);
2166   char *c;
2167   int lines = 0;
2168   int max = bp->max_lines;
2169
2170   /* Fewer lines on iPhone */
2171   if ((mi->xgwa.height <= 640 || mi->xgwa.width <= 640) &&
2172       max > 4)
2173     max = 4;
2174
2175   for (c = bp->words; c < p; c++)
2176     if (*c == '\n')
2177       lines++;
2178
2179   while (p < bp->words + sizeof(bp->words) - 1 &&
2180          lines < max)
2181     {
2182       int c = textclient_getc (bp->tc);
2183       if (c == '\n')
2184         lines++;
2185       if (c > 0)
2186         *p++ = (char) c;
2187       else
2188         break;
2189     }
2190   *p = 0;
2191
2192   bp->lines = lines;
2193 }
2194
2195
2196 static void
2197 bubble (ModeInfo *mi)
2198 {
2199   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2200   int duration = 200;
2201   GLfloat fade = 0.015;
2202   int chance = (talk_chance <= 0.0  ? 0 :
2203                 talk_chance >= 0.99 ? 1 :
2204                 (1-talk_chance) * 1000);
2205   GLfloat scale;
2206   char *s0 = strdup (bp->words);
2207   char *s = s0;
2208   int L;
2209
2210   while (*s == '\n') s++;
2211   L = strlen(s);
2212   while (L > 0 && (s[L-1] == '\n' || s[L-1] == ' ' || s[L-1] == '\t'))
2213     s[--L] = 0;
2214   if (! *s) goto DONE;
2215
2216 # ifdef DEBUG
2217   if (debug_p) goto DONE;
2218 # endif
2219
2220   if (chance <= 0) goto DONE;
2221
2222   if (bp->bubble_tick > 0)
2223     {
2224       bp->bubble_tick--;
2225       if (! bp->bubble_tick)
2226         *bp->words = 0;
2227     }
2228
2229   if (! bp->bubble_tick)
2230     {
2231       if (!(random() % chance))
2232         bp->bubble_tick = duration;
2233       else
2234         goto DONE;
2235     }
2236
2237   scale = (bp->bubble_tick < duration * fade
2238            ? bp->bubble_tick / (duration * fade)
2239            : (bp->bubble_tick > duration * (1 - fade)
2240               ? 1 - ((bp->bubble_tick - duration * (1 - fade))
2241                      / (duration * fade))
2242               : 1));
2243
2244   draw_label (mi, &bp->walkers[0], 1.5, scale, s);
2245
2246  DONE:
2247   free (s0);
2248 }
2249 #endif /* WORDBUBBLES */
2250
2251
2252
2253 typedef struct {
2254   int i;
2255   GLdouble d;
2256 } depth_sorter;
2257
2258 static int
2259 cmp_depth_sorter (const void *aa, const void *bb)
2260 {
2261   const depth_sorter *a = (depth_sorter *) aa;
2262   const depth_sorter *b = (depth_sorter *) bb;
2263   return (a->d == b->d ? 0 : a->d < b->d ? -1 : 1);
2264 }
2265
2266
2267 ENTRYPOINT void
2268 draw_robot (ModeInfo *mi)
2269 {
2270   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2271   Display *dpy = MI_DISPLAY(mi);
2272   Window window = MI_WINDOW(mi);
2273   GLfloat robot_size;
2274   depth_sorter *sorted;
2275   int i;
2276
2277   if (!bp->glx_context)
2278     return;
2279
2280   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
2281
2282   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2283
2284   glPushMatrix ();
2285   glRotatef(current_device_rotation(), 0, 0, 1);
2286   gltrackball_rotate (bp->user_trackball);
2287
2288   robot_size = size * 7;
2289   glScalef (robot_size, robot_size, robot_size);
2290   look_at_center (mi);
2291
2292   glPushMatrix();
2293   glScalef (1/robot_size, 1/robot_size, 1/robot_size);
2294   glCallList (bp->dlists[GROUND]);
2295   glPopMatrix();
2296
2297 # ifdef WORDBUBBLES
2298   fill_words (mi);
2299   bubble (mi);
2300 # endif
2301
2302 #ifdef DEBUG
2303   if (debug_p)
2304     {
2305       glTranslatef (0, -1.2, 0);
2306       glScalef (3, 3, 3);
2307       glRotatef (-43.5, 0, 0, 1);
2308       glRotatef (-90, 0, 1, 0);
2309
2310       /* glTranslatef (bp->debug_z, bp->debug_y, 0); */
2311
2312       glPushMatrix();
2313       if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
2314       if (!MI_IS_WIREFRAME(mi) && do_texture) glDisable(GL_TEXTURE_2D);
2315
2316       glBegin(GL_LINES);
2317       glVertex3f(-10, 0, 0); glVertex3f(10, 0, 0);
2318       glVertex3f(0, -10, 0); glVertex3f(0, 10, 0);
2319       glVertex3f(0, 0, -10); glVertex3f(0, 0, 10);
2320       glEnd();
2321
2322       glTranslatef (-0.5, 0, -0.5);
2323
2324       glColor3f (1, 0, 0);
2325       glBegin (GL_LINE_LOOP);
2326       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2327       glVertex3f (0, 1, 1); glVertex3f (0, 1, 0);
2328       glEnd();
2329
2330       glBegin (GL_LINE_LOOP);
2331       glVertex3f (1, 0, 0); glVertex3f (1, 0, 1);
2332       glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
2333       glEnd();
2334
2335 # if 1
2336       glColor3f (0.5, 0.5, 0.5);
2337       glFrontFace (GL_CCW);
2338       glBegin (GL_QUADS);
2339       /* glVertex3f (0, 1, 0); glVertex3f (0, 1, 1); */
2340       /* glVertex3f (1, 1, 1); glVertex3f (1, 1, 0); */
2341       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2342       glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
2343       glEnd();
2344
2345       glFrontFace (GL_CW);
2346       glBegin (GL_QUADS);
2347       glVertex3f (0, 1, 0); glVertex3f (0, 1, 1);
2348       glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
2349       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2350       glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
2351       glEnd();
2352 # endif
2353
2354       glColor3f (1, 0, 0);
2355       glBegin (GL_LINES);
2356       glVertex3f (0, 0, 0); glVertex3f (1, 0, 0);
2357       glVertex3f (0, 0, 1); glVertex3f (1, 0, 1);
2358       glVertex3f (0, 1, 0); glVertex3f (1, 1, 0);
2359       glVertex3f (0, 1, 1); glVertex3f (1, 1, 1);
2360
2361       glVertex3f (0, 0, 0); glVertex3f (1, 0, 1);
2362       glVertex3f (0, 0, 1); glVertex3f (1, 0, 0);
2363       glVertex3f (0, 1, 0); glVertex3f (1, 1, 1);
2364       glVertex3f (0, 1, 1); glVertex3f (1, 1, 0);
2365       glEnd();
2366
2367       if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
2368       if (!MI_IS_WIREFRAME(mi) && do_texture) glEnable(GL_TEXTURE_2D);
2369
2370       glPopMatrix();
2371     }
2372
2373 # endif /* DEBUG */
2374
2375   /* For transparency to work right, we have to draw the robots from
2376      back to front, from the perspective of the observer.  So project
2377      the origin of the robot to screen coordinates, and sort that by Z.
2378    */
2379   sorted = (depth_sorter *) calloc (bp->nwalkers, sizeof (*sorted));
2380   {
2381     GLdouble m[16], p[16];
2382     GLint v[4];
2383     glGetDoublev (GL_MODELVIEW_MATRIX, m);
2384     glGetDoublev (GL_PROJECTION_MATRIX, p);
2385     glGetIntegerv (GL_VIEWPORT, v);
2386
2387     for (i = 0; i < bp->nwalkers; i++)
2388       {
2389         GLdouble x, y, z;
2390         walker *f = &bp->walkers[i];
2391         gluProject (f->y, f->z, f->x, m, p, v, &x, &y, &z);
2392         sorted[i].i = i;
2393         sorted[i].d = -z;
2394       }
2395     qsort (sorted, i, sizeof(*sorted), cmp_depth_sorter);
2396   }
2397
2398
2399   mi->polygon_count = 0;
2400   for (i = 0; i < bp->nwalkers; i++)
2401     {
2402       int ii = sorted[i].i;
2403       walker *f = &bp->walkers[ii];
2404       int ticks = 22 * speed * f->speed;
2405       int max = 180;
2406       char tag[1024];
2407       *tag = 0;
2408
2409       if (ticks < 1) ticks = 1;
2410       if (ticks > max) ticks = max;
2411
2412 # ifdef DEBUG
2413       if (debug_p)
2414         sprintf (tag, "%.4f, %.4f,  %.4f",
2415                  bp->debug_x, bp->debug_y, bp->debug_z);
2416       else
2417         {
2418 #  if 1
2419           /* sprintf (tag + strlen(tag), "    %d\n", ii); */
2420           sprintf (tag + strlen(tag), "    %d\n", bp->nwalkers - i - 1);
2421           /* sprintf (tag + strlen(tag), "%.03f\n", sorted[i].d); */
2422 #  endif
2423         }
2424
2425 # endif /* DEBUG */
2426
2427       mi->polygon_count += draw_walker (mi, f, tag);
2428
2429       for (ii = 0; ii < ticks; ii++)
2430         tick_walker (mi, f);
2431     }
2432   free (sorted);
2433
2434
2435   glPopMatrix ();
2436
2437   if (mi->fps_p) do_fps (mi);
2438   glFinish();
2439
2440   glXSwapBuffers(dpy, window);
2441 }
2442
2443 ENTRYPOINT void
2444 release_robot (ModeInfo *mi)
2445 {
2446 # ifdef WORDBUBBLES
2447   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2448   textclient_close (bp->tc);
2449 # endif
2450 }
2451
2452 XSCREENSAVER_MODULE_2 ("WindupRobot", winduprobot, robot)
2453
2454 #endif /* USE_GL */