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