From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[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 #define 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 "glxfonts.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)
1609 {
1610   int count = 0;
1611   glPushMatrix();
1612
1613   glTranslatef (f->y, f->z, f->x);
1614
1615   {
1616     GLfloat n = 0.01;
1617     glScalef (n, n, n);
1618   }
1619
1620   glRotatef (90, 0, 1, 0);
1621   glRotatef (f->facing, 0, 1, 0);
1622   glRotatef (f->pitch,  0, 0, 1);
1623   glRotatef (f->roll,   1, 0, 0);
1624
1625   {
1626     GLfloat n = 0.00484;           /* make it 1 unit tall */
1627     glScalef (n, n, n);
1628   }
1629
1630   count += draw_gearbox (mi);
1631   count += draw_crank (mi, f, f->crank_rot);
1632   count += draw_rotator (mi, f, f->crank_rot);
1633   count += draw_leg (mi, f->crank_rot, False);
1634   count += draw_leg (mi, f->crank_rot, True);
1635   count += draw_wireframe (mi, f);
1636
1637   /* Draw these last, and outer shell first, to make transparency work.
1638      The order in which things hit the depth buffer matters.
1639    */
1640  if (f->body_transparency >= 0.001)
1641    {
1642      count += draw_arm (mi, f, True,  f->hand_rot[0], f->hand_pos[0]);
1643      count += draw_arm (mi, f, False, f->hand_rot[1], f->hand_pos[1]);
1644      count += draw_body (mi, f, False);
1645      count += draw_body (mi, f, True);
1646      count += draw_dome (mi, f);
1647    }
1648
1649   glPopMatrix();
1650   return count;
1651 }
1652
1653
1654 static int
1655 draw_ground (ModeInfo *mi, GLfloat color[4])
1656 {
1657   int wire = MI_IS_WIREFRAME(mi);
1658   GLfloat i;
1659   GLfloat cell_size = 0.9;
1660   int cells = 1000 * size;
1661   int points = 0;
1662
1663 # ifdef DEBUG
1664   if (debug_p) return 0;
1665 # endif
1666
1667   glPushMatrix();
1668
1669   glRotatef (frand(90), 0, 0, 1);
1670
1671   if (!wire)
1672     {
1673       GLfloat fog_color[4] = { 0, 0, 0, 1 };
1674
1675       glLineWidth (2);
1676       glEnable (GL_LINE_SMOOTH);
1677       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
1678       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
1679       glEnable (GL_BLEND);
1680
1681       glFogi (GL_FOG_MODE, GL_EXP2);
1682       glFogfv (GL_FOG_COLOR, fog_color);
1683       glFogf (GL_FOG_DENSITY, 0.017);
1684       glFogf (GL_FOG_START, -cells/2 * cell_size);
1685 # ifndef USE_IPHONE  /* #### Not working on iOS for some reason */
1686       glEnable (GL_FOG);
1687 # endif
1688     }
1689
1690   glColor4fv (color);
1691   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
1692
1693   glBegin (GL_LINES);
1694   for (i = -cells/2; i < cells/2; i++)
1695     {
1696       GLfloat a = i * cell_size;
1697       GLfloat b = cells/2 * cell_size;
1698       glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++;
1699       glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++;
1700     }
1701   glEnd();
1702
1703   if (!wire)
1704     {
1705       glDisable (GL_LINE_SMOOTH);
1706       glDisable (GL_BLEND);
1707       glDisable (GL_FOG);
1708     }
1709
1710   glPopMatrix();
1711
1712   return points;
1713 }
1714
1715
1716 /* If the target robot (robot #0) has moved too far from the point at which
1717    the camera is aimed, then initiate an animation to move the observer to
1718    the new spot.
1719
1720    Because of the jerky forward motion of the robots, just always focusing
1721    on the center of the robot looks terrible, so instead we let them walk
1722    a little out of the center of the frame, and then catch up.
1723  */
1724 static void
1725 look_at_center (ModeInfo *mi)
1726 {
1727   robot_configuration *bp = &bps[MI_SCREEN(mi)];
1728   GLfloat target_x = bp->walkers[0].x;
1729   GLfloat target_y = bp->walkers[0].y;
1730   GLfloat target_z = 0.8;   /* Look a little bit above his head */
1731   GLfloat max_dist = 2.5 / size;
1732
1733 # ifdef DEBUG
1734   if (debug_p) return;
1735 # endif
1736
1737   if (max_dist < 1)  max_dist = 1;
1738   if (max_dist > 10) max_dist = 10;
1739
1740   if (bp->camera_tracking_p)
1741     {
1742       GLfloat r = (1 - cos (bp->tracking_ratio * M_PI)) / 2;
1743       bp->looking_x = bp->olooking_x + r * (target_x - bp->olooking_x);
1744       bp->looking_y = bp->olooking_y + r * (target_y - bp->olooking_y);
1745       bp->looking_z = bp->olooking_z + r * (target_z - bp->olooking_z);
1746
1747       bp->tracking_ratio += 0.02;
1748       if (bp->tracking_ratio >= 1)
1749         {
1750           bp->camera_tracking_p = False;
1751           bp->olooking_x = bp->looking_x;
1752           bp->olooking_y = bp->looking_y;
1753           bp->olooking_z = bp->looking_z;
1754         }
1755     }
1756           
1757   if (! bp->camera_tracking_p)
1758     {
1759       GLfloat dist =
1760         sqrt ((target_x - bp->looking_x) * (target_x - bp->looking_x) +
1761               (target_y - bp->looking_y) * (target_y - bp->looking_y) +
1762               (target_z - bp->looking_z) * (target_z - bp->looking_z));
1763
1764       if (dist > max_dist)
1765         {
1766           bp->camera_tracking_p = True;
1767           bp->tracking_ratio = 0;
1768           bp->olooking_x = bp->looking_x;
1769           bp->olooking_y = bp->looking_y;
1770           bp->olooking_z = bp->looking_z;
1771         }
1772     }
1773
1774   glTranslatef (-bp->looking_y, -bp->looking_z, -bp->looking_x);
1775
1776 # if 0 /* DEBUG */
1777   {
1778     GLfloat th;
1779     glPushMatrix();
1780     glColor3f(1, 0, 0);
1781     glTranslatef (target_y, target_z, target_x);
1782     glBegin(GL_LINES);
1783     glVertex3f(0, -target_z, 0);
1784     glVertex3f(0, 1, 0);
1785     glVertex3f(-0.1, 0, -0.1);
1786     glVertex3f( 0.1, 0,  0.1);
1787     glVertex3f(-0.1, 0,  0.1);
1788     glVertex3f( 0.1, 0, -0.1);
1789     glEnd();
1790     glPopMatrix();
1791
1792     glPushMatrix();
1793     glColor3f(0, 1, 0);
1794     glTranslatef (bp->looking_y, bp->looking_z, bp->looking_x);
1795     glRotatef (30, 0, 1, 0);
1796     glBegin(GL_LINES);
1797     glVertex3f(0, -bp->looking_z, 0);
1798     glVertex3f(0, 1, 0);
1799     glVertex3f(-0.1, 0, -0.1);
1800     glVertex3f( 0.1, 0,  0.1);
1801     glVertex3f(-0.1, 0,  0.1);
1802     glVertex3f( 0.1, 0, -0.1);
1803     glEnd();
1804     glPopMatrix();
1805
1806     glPushMatrix();
1807     glColor3f(0, 0, 1);
1808     glTranslatef (bp->olooking_y, bp->olooking_z, bp->olooking_x);
1809     glRotatef (60, 0, 1, 0);
1810     glBegin(GL_LINES);
1811     glVertex3f(0, -bp->olooking_z, 0);
1812     glVertex3f(0, 1, 0);
1813     glVertex3f(-0.1, 0, -0.1);
1814     glVertex3f( 0.1, 0,  0.1);
1815     glVertex3f(-0.1, 0,  0.1);
1816     glVertex3f( 0.1, 0, -0.1);
1817     glEnd();
1818
1819     glTranslatef (0, -bp->olooking_z, 0);
1820     glBegin (GL_LINE_LOOP);
1821     for (th = 0; th < M_PI * 2; th += 0.1)
1822       glVertex3f (bp->olooking_y + max_dist * cos(th), 0,
1823                   bp->olooking_x + max_dist * sin(th));
1824     glEnd();
1825     glPopMatrix();
1826   }
1827 # endif /* DEBUG */
1828 }
1829
1830
1831 #ifdef WORDBUBBLES
1832
1833 /* Draw a cartoony word bubble.
1834    W and H are the inside size, for text.
1835    Origin is at bottom left.
1836    The bubble frame and arrow are outside that.
1837  */
1838 static void
1839 draw_bubble_box (ModeInfo *mi,
1840                  GLfloat width, GLfloat height,
1841                  GLfloat corner_radius,
1842                  GLfloat arrow_h, GLfloat arrow_x,
1843                  GLfloat fg[4], GLfloat bg[4])
1844 {
1845
1846 # define CORNER_POINTS 16
1847   GLfloat outline_points[ (CORNER_POINTS + 2) * 4 + 8 ][3];
1848   int i = 0;
1849   GLfloat th;
1850   GLfloat tick = M_PI / 2 / CORNER_POINTS;
1851
1852   GLfloat arrow_w = arrow_h / 2;
1853   GLfloat arrow_x2 = MAX(0, MIN(width - arrow_w, arrow_x));
1854
1855   GLfloat w2 = MAX(arrow_w, width  - corner_radius * 1.10);
1856   GLfloat h2 = MAX(0,       height - corner_radius * 1.28);
1857   GLfloat x2 = (width  - w2) / 2;
1858   GLfloat y2 = (height - h2) / 2;
1859                                         /*        A  B         C   D    */
1860   GLfloat xa = x2 -corner_radius;       /*    E     _------------_      */
1861   GLfloat xb = x2;                      /*    D   /__|         |__\     */
1862   GLfloat xc = xb + w2;                 /*        |  |         |  |     */
1863   GLfloat xd = xc + corner_radius;      /*    C   |__|   EF    |__|     */
1864   GLfloat xe = xb + arrow_x2;           /*    B    \_|_________|_/      */
1865   GLfloat xf = xe + arrow_w;            /*    A          \|             */
1866
1867   GLfloat ya = y2 - (corner_radius + arrow_h);
1868   GLfloat yb = y2 - corner_radius;
1869   GLfloat yc = y2;
1870   GLfloat yd = yc + h2;
1871   GLfloat ye = yd + corner_radius;
1872
1873   GLfloat z = 0;
1874
1875   /* Let the lines take precedence over the fills. */
1876   glEnable (GL_POLYGON_OFFSET_FILL);
1877   glPolygonOffset (1.0, 1.0);
1878
1879   glColor4fv (bg);
1880   glFrontFace(GL_CW);
1881
1882   /* top left corner */
1883
1884   glBegin (GL_TRIANGLE_FAN);
1885   glVertex3f (xb, yd, 0);
1886   for (th = 0; th < M_PI/2 + tick; th += tick)
1887     {
1888       GLfloat x = xb - corner_radius * cos(th);
1889       GLfloat y = yd + corner_radius * sin(th);
1890       glVertex3f (x, y, z);
1891       outline_points[i][0] = x;
1892       outline_points[i][1] = y;
1893       outline_points[i][2] = z;
1894       i++;
1895     }
1896   glEnd();
1897
1898   /* top edge */
1899   outline_points[i][0] = xc;
1900   outline_points[i][1] = ye;
1901   outline_points[i][2] = z;
1902   i++;
1903
1904   /* top right corner */
1905
1906   glBegin (GL_TRIANGLE_FAN);
1907   glVertex3f (xc, yd, 0);
1908   for (th = M_PI/2; th > -tick; th -= tick)
1909     {
1910       GLfloat x = xc + corner_radius * cos(th);
1911       GLfloat y = yd + corner_radius * sin(th);
1912       glVertex3f (x, y, z);
1913       outline_points[i][0] = x;
1914       outline_points[i][1] = y;
1915       outline_points[i][2] = z;
1916       i++;
1917     }
1918   glEnd();
1919
1920   /* right edge */
1921   outline_points[i][0] = xd;
1922   outline_points[i][1] = yc;
1923   outline_points[i][2] = z;
1924   i++;
1925
1926   /* bottom right corner */
1927
1928   glBegin (GL_TRIANGLE_FAN);
1929   glVertex3f (xc, yc, 0);
1930   for (th = 0; th < M_PI/2 + tick; th += tick)
1931     {
1932       GLfloat x = xc + corner_radius * cos(th);
1933       GLfloat y = yc - corner_radius * sin(th);
1934       glVertex3f (x, y, z);
1935       outline_points[i][0] = x;
1936       outline_points[i][1] = y;
1937       outline_points[i][2] = z;
1938       i++;
1939     }
1940   glEnd();
1941
1942   /* bottom right edge */
1943   outline_points[i][0] = xf;
1944   outline_points[i][1] = yb;
1945   outline_points[i][2] = z;
1946   i++;
1947
1948   /* arrow triangle */
1949   glFrontFace(GL_CW);
1950   glBegin (GL_TRIANGLES);
1951
1952   /* bottom arrow point */
1953   outline_points[i][0] = xf;
1954   outline_points[i][1] = yb;
1955   outline_points[i][2] = z;
1956   glVertex3f (outline_points[i][0],
1957               outline_points[i][1],
1958               outline_points[i][2]);
1959   i++;
1960
1961   /* bottom right edge */
1962   outline_points[i][0] = xf;
1963   outline_points[i][1] = ya;
1964   outline_points[i][2] = z;
1965   glVertex3f (outline_points[i][0],
1966               outline_points[i][1],
1967               outline_points[i][2]);
1968   i++;
1969
1970   outline_points[i][0] = xe;
1971   outline_points[i][1] = yb;
1972   outline_points[i][2] = z;
1973   glVertex3f (outline_points[i][0],
1974               outline_points[i][1],
1975               outline_points[i][2]);
1976   i++;
1977   glEnd();
1978
1979
1980   /* bottom left corner */
1981
1982   glBegin (GL_TRIANGLE_FAN);
1983   glVertex3f (xb, yc, 0);
1984   for (th = M_PI/2; th > -tick; th -= tick)
1985     {
1986       GLfloat x = xb - corner_radius * cos(th);
1987       GLfloat y = yc - corner_radius * sin(th);
1988       glVertex3f (x, y, z);
1989       outline_points[i][0] = x;
1990       outline_points[i][1] = y;
1991       outline_points[i][2] = z;
1992       i++;
1993     }
1994   glEnd();
1995
1996   glFrontFace(GL_CCW);
1997
1998   /* left edge */
1999   outline_points[i][0] = xa;
2000   outline_points[i][1] = yd;
2001   outline_points[i][2] = z;
2002   i++;
2003
2004   glFrontFace(GL_CW);
2005   glBegin (GL_QUADS);
2006   /* left box */
2007   glVertex3f (xa, yd, z);
2008   glVertex3f (xb, yd, z);
2009   glVertex3f (xb, yc, z);
2010   glVertex3f (xa, yc, z);
2011
2012   /* center box */
2013   glVertex3f (xb, ye, z);
2014   glVertex3f (xc, ye, z);
2015   glVertex3f (xc, yb, z);
2016   glVertex3f (xb, yb, z);
2017
2018   /* right box */
2019   glVertex3f (xc, yd, z);
2020   glVertex3f (xd, yd, z);
2021   glVertex3f (xd, yc, z);
2022   glVertex3f (xc, yc, z);
2023
2024   glEnd();
2025
2026   glLineWidth (2.8);
2027   glColor4fv (fg);
2028
2029   glBegin (GL_LINE_LOOP);
2030   while (i > 0)
2031     glVertex3fv (outline_points[--i]);
2032   glEnd();
2033
2034   glDisable (GL_POLYGON_OFFSET_FILL);
2035 }
2036
2037
2038 static void
2039 draw_label (ModeInfo *mi, walker *f, GLfloat y_off, GLfloat scale,
2040             const char *label)
2041 {
2042   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2043   int wire = MI_IS_WIREFRAME(mi);
2044   GLfloat m[4][4];
2045
2046   if (scale == 0) return;
2047
2048   if (!wire)
2049     glDisable (GL_LIGHTING);   /* don't light fonts */
2050
2051   glPushMatrix();
2052
2053   /* First, we translate the origin to the center of the robot.
2054
2055      Then we retrieve the prevailing modelview matrix, which
2056      includes any rotation, wandering, and user-trackball-rolling
2057      of the scene.
2058
2059      We set the top 3x3 cells of that matrix to be the identity
2060      matrix.  This removes all rotation from the matrix, while
2061      leaving the translation alone.  This has the effect of
2062      leaving the prevailing coordinate system perpendicular to
2063      the camera view: were we to draw a square face, it would
2064      be in the plane of the screen.
2065
2066      Now we translate by `size' toward the viewer -- so that the
2067      origin is *just in front* of the ball.
2068
2069      Then we draw the label text, allowing the depth buffer to
2070      do its work: that way, labels on atoms will be occluded
2071      properly when other atoms move in front of them.
2072
2073      This technique (of neutralizing rotation relative to the
2074      observer, after both rotations and translations have been
2075      applied) is known as "billboarding".
2076    */
2077
2078   if (f)
2079     glTranslatef(f->y, 0, f->x);                   /* get matrix */
2080
2081   glTranslatef (0, y_off, 0);
2082
2083   glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);  /* load rot. identity */
2084   m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
2085   m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
2086   m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
2087   glLoadIdentity();                             /* reset modelview */
2088   glMultMatrixf (&m[0][0]);                     /* replace with ours */
2089
2090   glTranslatef (0, 0, 0.1);                     /* move toward camera */
2091
2092   glRotatef (current_device_rotation(), 0, 0, 1);  /* right side up */
2093
2094   {
2095     int cw, ch, w, h;
2096     GLfloat s;
2097     GLfloat max = 24;   /* max point size to avoid pixellated text */
2098
2099     /* Let the font be much larger on iPhone */
2100     if (mi->xgwa.height <= 640 || mi->xgwa.width <= 640)
2101       max *= 3;
2102     
2103     cw = texture_string_width (bp->font_data, "X", &ch);  /* line height */
2104     s = 1.0 / ch;
2105     if (ch > max) s *= max/ch;
2106
2107     s *= scale;
2108
2109     w = texture_string_width (bp->font_data, label, &h);
2110
2111     glScalef (s, s, 1);
2112     glTranslatef (-w/2, h*2/3 + (cw * 7), 0);
2113
2114     glPushMatrix();
2115     glTranslatef (0, -h - ch/4, -0.1);
2116     draw_bubble_box (mi, w, h, 
2117                      ch * 2,            /* corner radius */
2118                      ch * 2.5,          /* arrow height */
2119                      w / 2 - cw * 8,    /* arrow x */
2120                      bp->text_bd, bp->text_bg);
2121     glPopMatrix();
2122
2123     glColor4fv (bp->text_color);
2124     print_gl_string (mi->dpy, bp->font_data,
2125                      0, 0, 0, 0,
2126                      label, False);
2127   }
2128
2129   glPopMatrix();
2130
2131   /* More efficient to always call glEnable() with correct values
2132      than to call glPushAttrib()/glPopAttrib(), since reading
2133      attributes from GL does a round-trip and  stalls the pipeline.
2134    */
2135   if (!wire)
2136     glEnable (GL_LIGHTING);
2137 }
2138
2139
2140 static void
2141 fill_words (ModeInfo *mi)
2142 {
2143   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2144   char *p = bp->words + strlen(bp->words);
2145   char *c;
2146   int lines = 0;
2147   int max = bp->max_lines;
2148
2149   /* Fewer lines on iPhone */
2150   if ((mi->xgwa.height <= 640 || mi->xgwa.width <= 640) &&
2151       max > 4)
2152     max = 4;
2153
2154   for (c = bp->words; c < p; c++)
2155     if (*c == '\n')
2156       lines++;
2157
2158   while (p < bp->words + sizeof(bp->words) - 1 &&
2159          lines < max)
2160     {
2161       int c = textclient_getc (bp->tc);
2162       if (c == '\n')
2163         lines++;
2164       if (c > 0)
2165         *p++ = (char) c;
2166       else
2167         break;
2168     }
2169   *p = 0;
2170
2171   bp->lines = lines;
2172 }
2173
2174
2175 static void
2176 bubble (ModeInfo *mi)
2177 {
2178   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2179   int duration = 200;
2180   GLfloat fade = 0.015;
2181   int chance = (talk_chance <= 0.0  ? 0 :
2182                 talk_chance >= 0.99 ? 1 :
2183                 (1-talk_chance) * 1000);
2184   GLfloat scale;
2185   char *s0 = strdup (bp->words);
2186   char *s = s0;
2187   int L;
2188
2189   while (*s == '\n') s++;
2190   L = strlen(s);
2191   while (L > 0 && (s[L-1] == '\n' || s[L-1] == ' ' || s[L-1] == '\t'))
2192     s[--L] = 0;
2193   if (! *s) goto DONE;
2194
2195 # ifdef DEBUG
2196   if (debug_p) goto DONE;
2197 # endif
2198
2199   if (chance <= 0) goto DONE;
2200
2201   if (bp->bubble_tick > 0)
2202     {
2203       bp->bubble_tick--;
2204       if (! bp->bubble_tick)
2205         *bp->words = 0;
2206     }
2207
2208   if (! bp->bubble_tick)
2209     {
2210       if (!(random() % chance))
2211         bp->bubble_tick = duration;
2212       else
2213         goto DONE;
2214     }
2215
2216   scale = (bp->bubble_tick < duration * fade
2217            ? bp->bubble_tick / (duration * fade)
2218            : (bp->bubble_tick > duration * (1 - fade)
2219               ? 1 - ((bp->bubble_tick - duration * (1 - fade))
2220                      / (duration * fade))
2221               : 1));
2222
2223   draw_label (mi, &bp->walkers[0], 1.5, scale, s);
2224
2225  DONE:
2226   free (s0);
2227 }
2228 #endif /* WORDBUBBLES */
2229
2230
2231
2232 ENTRYPOINT void
2233 draw_robot (ModeInfo *mi)
2234 {
2235   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2236   Display *dpy = MI_DISPLAY(mi);
2237   Window window = MI_WINDOW(mi);
2238   GLfloat robot_size;
2239   int i;
2240
2241   if (!bp->glx_context)
2242     return;
2243
2244   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
2245
2246   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2247
2248   glPushMatrix ();
2249   glRotatef(current_device_rotation(), 0, 0, 1);
2250   gltrackball_rotate (bp->user_trackball);
2251
2252   robot_size = size * 7;
2253   glScalef (robot_size, robot_size, robot_size);
2254   look_at_center (mi);
2255
2256   glPushMatrix();
2257   glScalef (1/robot_size, 1/robot_size, 1/robot_size);
2258   glCallList (bp->dlists[GROUND]);
2259   glPopMatrix();
2260
2261 # ifdef WORDBUBBLES
2262   fill_words (mi);
2263   bubble (mi);
2264 # endif
2265
2266 #ifdef DEBUG
2267   if (debug_p)
2268     {
2269       glTranslatef (0, -1.2, 0);
2270       glScalef (3, 3, 3);
2271       glRotatef (-43.5, 0, 0, 1);
2272       glRotatef (-90, 0, 1, 0);
2273
2274       /* glTranslatef (bp->debug_z, bp->debug_y, 0); */
2275
2276       glPushMatrix();
2277       if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
2278       if (!MI_IS_WIREFRAME(mi) && do_texture) glDisable(GL_TEXTURE_2D);
2279
2280       glBegin(GL_LINES);
2281       glVertex3f(-10, 0, 0); glVertex3f(10, 0, 0);
2282       glVertex3f(0, -10, 0); glVertex3f(0, 10, 0);
2283       glVertex3f(0, 0, -10); glVertex3f(0, 0, 10);
2284       glEnd();
2285
2286       glTranslatef (-0.5, 0, -0.5);
2287
2288       glColor3f (1, 0, 0);
2289       glBegin (GL_LINE_LOOP);
2290       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2291       glVertex3f (0, 1, 1); glVertex3f (0, 1, 0);
2292       glEnd();
2293
2294       glBegin (GL_LINE_LOOP);
2295       glVertex3f (1, 0, 0); glVertex3f (1, 0, 1);
2296       glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
2297       glEnd();
2298
2299 # if 1
2300       glColor3f (0.5, 0.5, 0.5);
2301       glFrontFace (GL_CCW);
2302       glBegin (GL_QUADS);
2303       /* glVertex3f (0, 1, 0); glVertex3f (0, 1, 1); */
2304       /* glVertex3f (1, 1, 1); glVertex3f (1, 1, 0); */
2305       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2306       glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
2307       glEnd();
2308
2309       glFrontFace (GL_CW);
2310       glBegin (GL_QUADS);
2311       glVertex3f (0, 1, 0); glVertex3f (0, 1, 1);
2312       glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
2313       glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
2314       glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
2315       glEnd();
2316 # endif
2317
2318       glColor3f (1, 0, 0);
2319       glBegin (GL_LINES);
2320       glVertex3f (0, 0, 0); glVertex3f (1, 0, 0);
2321       glVertex3f (0, 0, 1); glVertex3f (1, 0, 1);
2322       glVertex3f (0, 1, 0); glVertex3f (1, 1, 0);
2323       glVertex3f (0, 1, 1); glVertex3f (1, 1, 1);
2324
2325       glVertex3f (0, 0, 0); glVertex3f (1, 0, 1);
2326       glVertex3f (0, 0, 1); glVertex3f (1, 0, 0);
2327       glVertex3f (0, 1, 0); glVertex3f (1, 1, 1);
2328       glVertex3f (0, 1, 1); glVertex3f (1, 1, 0);
2329       glEnd();
2330
2331       if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
2332       if (!MI_IS_WIREFRAME(mi) && do_texture) glEnable(GL_TEXTURE_2D);
2333
2334       glPopMatrix();
2335     }
2336
2337 # endif /* DEBUG */
2338
2339   mi->polygon_count = 0;
2340   for (i = 0; i < bp->nwalkers; i++)
2341     {
2342       walker *f = &bp->walkers[i];
2343       int i, ticks = 22 * speed * f->speed;
2344       int max = 180;
2345
2346       if (ticks < 1) ticks = 1;
2347       if (ticks > max) ticks = max;
2348
2349       mi->polygon_count += draw_walker (mi, f);
2350
2351 # ifdef DEBUG
2352       if (debug_p)
2353         {
2354           char s[1024];
2355           sprintf (s, "%.4f, %.4f,  %.4f",
2356                    bp->debug_x, bp->debug_y, bp->debug_z);
2357           glColor3f (1, 1, 1);
2358           draw_label (mi, f, -0.3, 1, s);
2359         }
2360 # endif /* DEBUG */
2361
2362       for (i = 0; i < ticks; i++)
2363         tick_walker (mi, f);
2364     }
2365
2366
2367   glPopMatrix ();
2368
2369   if (mi->fps_p) do_fps (mi);
2370   glFinish();
2371
2372   glXSwapBuffers(dpy, window);
2373 }
2374
2375 ENTRYPOINT void
2376 release_robot (ModeInfo *mi)
2377 {
2378 # ifdef WORDBUBBLES
2379   robot_configuration *bp = &bps[MI_SCREEN(mi)];
2380   textclient_close (bp->tc);
2381 # endif
2382 }
2383
2384 XSCREENSAVER_MODULE_2 ("WindupRobot", winduprobot, robot)
2385
2386 #endif /* USE_GL */