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