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