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