summaryrefslogtreecommitdiffstats
path: root/hacks/glx/winduprobot.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/winduprobot.c')
-rw-r--r--hacks/glx/winduprobot.c2483
1 files changed, 2483 insertions, 0 deletions
diff --git a/hacks/glx/winduprobot.c b/hacks/glx/winduprobot.c
new file mode 100644
index 0000000..61091cf
--- /dev/null
+++ b/hacks/glx/winduprobot.c
@@ -0,0 +1,2483 @@
+/* winduprobot, Copyright (c) 2014-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * Draws a robot wind-up toy.
+ *
+ * I've had this little robot since I was about six years old! When the time
+ * came for us to throw the Cocktail Robotics Grand Challenge at DNA Lounge, I
+ * photographed this robot (holding a tiny martini glass) to make a flyer for
+ * the event. You can see that photo here:
+ * https://www.dnalounge.com/flyers/2014/09/14.html
+ *
+ * Then I decided to try and make award statues for the contest by modeling
+ * this robot and 3D-printing it (a robot on a post, with the DNA Lounge
+ * grommet around it.) So I learned Maya and built a model.
+ *
+ * Well, that 3D printing idea didn't work out, but since I had the model
+ * already, I exported it and turned it into a screen saver.
+ *
+ * The DXF files that Maya exports aren't simple enough for my dxf2gl.pl
+ * script to process, so the exporting process went:
+ *
+ * - Save from Maya to OBJ;
+ * - Import OBJ into SketchUp using
+ * http://www.scriptspot.com/sketchup/scripts/obj-importer
+ * - Clean up the model a little bit more;
+ * - Export to DXF with "Millimeters", "Triangles", using
+ * http://www.guitar-list.com/download-software/convert-sketchup-skp-files-dxf-or-stl
+ *
+ * We did eventually end up with robotic award statues, but we constructed
+ * them out of mass-produced wind-up robots, rather than 3D printing them:
+ * https://www.dnalounge.com/gallery/2014/09-14/045.html
+ * https://www.youtube.com/watch?v=EZF4ZAAy49g
+ */
+
+#define LABEL_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
+
+#define DEFAULTS "*delay: 20000 \n" \
+ "*count: 25 \n" \
+ "*showFPS: False \n" \
+ "*wireframe: False \n" \
+ "*labelFont: " LABEL_FONT "\n" \
+ "*legColor: #AA2222" "\n" \
+ "*armColor: #AA2222" "\n" \
+ "*handColor: #AA2222" "\n" \
+ "*crankColor: #444444" "\n" \
+ "*bodyColor: #7777AA" "\n" \
+ "*domeColor: #7777AA" "\n" \
+ "*insideColor: #DDDDDD" "\n" \
+ "*gearboxColor: #444488" "\n" \
+ "*gearColor: #008877" "\n" \
+ "*wheelColor: #007788" "\n" \
+ "*wireColor: #006600" "\n" \
+ "*groundColor: #0000FF" "\n" \
+ "*textColor: #FFFFFF""\n" \
+ "*textBackground: #444444""\n" \
+ "*textBorderColor: #FFFF88""\n" \
+ "*textLines: 10 \n" \
+ "*program: xscreensaver-text\n" \
+ "*usePty: False\n"
+
+#undef DEBUG
+#define WORDBUBBLES
+
+# define release_robot 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#define DEF_SPEED "1.0"
+#define DEF_ROBOT_SIZE "1.0"
+#define DEF_TEXTURE "True"
+#define DEF_FADE "True"
+#define DEF_OPACITY "1.0"
+#define DEF_TALK "0.2"
+
+#include "xlockmore.h"
+#include "gltrackball.h"
+#include "ximage-loader.h"
+#include "involute.h"
+#include "sphere.h"
+
+#ifdef WORDBUBBLES
+# include "textclient.h"
+# include "texfont.h"
+#endif
+
+#include <ctype.h>
+
+#ifndef HAVE_JWZGLES /* No SPHERE_MAP on iPhone */
+# define HAVE_TEXTURE
+#endif
+
+#ifdef HAVE_TEXTURE
+# include "images/gen/chromesphere_png.h"
+#endif
+
+#ifdef USE_GL /* whole file */
+
+#include "gllist.h"
+
+extern const struct gllist
+ *robot_arm_half, *robot_body_half_outside, *robot_body_half_inside,
+ *robot_crank_full, *robot_gearbox_half, *robot_hand_half,
+ *robot_leg_half, *robot_rotator_half, *robot_wireframe;
+
+static struct gllist *robot_dome = 0, *robot_gear = 0, *ground = 0;
+
+static const struct gllist * const *all_objs[] = {
+ &robot_arm_half, &robot_body_half_outside, &robot_body_half_inside,
+ &robot_crank_full, &robot_gearbox_half, &robot_hand_half,
+ &robot_leg_half, &robot_rotator_half, &robot_wireframe,
+ (const struct gllist * const *) &robot_dome,
+ (const struct gllist * const *) &robot_gear,
+ (const struct gllist * const *) &ground
+};
+
+#define ROBOT_ARM 0
+#define ROBOT_BODY_1 1
+#define ROBOT_BODY_2 2
+#define ROBOT_CRANK 3
+#define ROBOT_GEARBOX 4
+#define ROBOT_HAND 5
+#define ROBOT_LEG 6
+#define ROBOT_ROTATOR 7
+#define ROBOT_WIREFRAME 8
+#define ROBOT_DOME 9
+#define ROBOT_GEAR 10
+#define GROUND 11
+
+typedef struct {
+ GLfloat x, y, z; /* position */
+ GLfloat facing; /* direction of front of robot, degrees */
+ GLfloat pitch; /* front/back tilt angle, degrees */
+ GLfloat roll; /* left/right tilt angle, degrees */
+ GLfloat speed; /* some robots are faster */
+ GLfloat crank_rot; /* gear state, degrees */
+ GLfloat hand_rot[2]; /* rotation of the hands, degrees */
+ GLfloat hand_pos[2]; /* openness of the hands, ratio */
+ GLfloat balance; /* how off-true does it walk? degrees */
+ GLfloat body_transparency; /* ratio */
+ int fading_p; /* -1, 0, 1 */
+
+} walker;
+
+typedef struct {
+ GLXContext *glx_context;
+ trackball_state *user_trackball;
+ Bool button_down_p;
+
+ GLuint *dlists;
+ GLfloat component_colors[countof(all_objs)][4];
+
+ int nwalkers;
+ walker *walkers;
+
+ GLfloat looking_x, looking_y, looking_z; /* Where camera is aimed */
+ GLfloat olooking_x, olooking_y, olooking_z; /* Where camera was aimed */
+ Bool camera_tracking_p; /* Whether camera in motion */
+ GLfloat tracking_ratio;
+
+# ifdef HAVE_TEXTURE
+ GLuint chrome_texture;
+# endif
+
+# ifdef WORDBUBBLES
+ texture_font_data *font_data;
+ int bubble_tick;
+ text_data *tc;
+ char words[10240];
+ int lines, max_lines;
+
+ GLfloat text_color[4], text_bg[4], text_bd[4];
+
+# endif /* WORDBUBBLES */
+
+
+# ifdef DEBUG
+ GLfloat debug_x, debug_y, debug_z;
+# endif
+
+} robot_configuration;
+
+static robot_configuration *bps = NULL;
+
+static GLfloat speed, size, opacity;
+static int do_texture, do_fade;
+#ifdef WORDBUBBLES
+static GLfloat talk_chance;
+#endif
+#ifdef DEBUG
+static int debug_p;
+#endif
+
+static XrmOptionDescRec opts[] = {
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-size", ".robotSize", XrmoptionSepArg, 0 },
+ { "-opacity", ".opacity", XrmoptionSepArg, 0 },
+ { "-talk", ".talk", XrmoptionSepArg, 0 },
+ {"-texture", ".texture", XrmoptionNoArg, "True" },
+ {"+texture", ".texture", XrmoptionNoArg, "False" },
+ {"-fade", ".fade", XrmoptionNoArg, "True" },
+ {"+fade", ".fade", XrmoptionNoArg, "False" },
+#ifdef DEBUG
+ {"-debug", ".debug", XrmoptionNoArg, "True" },
+ {"+debug", ".debug", XrmoptionNoArg, "False" },
+#endif
+};
+
+static argtype vars[] = {
+ {&speed, "speed", "Speed", DEF_SPEED, t_Float},
+ {&size, "robotSize", "RobotSize", DEF_ROBOT_SIZE, t_Float},
+ {&opacity, "opacity", "Opacity", DEF_OPACITY, t_Float},
+ {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool},
+ {&do_fade, "fade", "Fade", DEF_FADE, t_Bool},
+#ifdef WORDBUBBLES
+ {&talk_chance, "talk", "Talk", DEF_TALK, t_Float},
+#endif
+#ifdef DEBUG
+ {&debug_p, "debug", "Debug", "False", t_Bool},
+#endif
+};
+
+ENTRYPOINT ModeSpecOpt robot_opts = {
+ countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_robot (ModeInfo *mi, int width, int height)
+{
+ GLfloat h = (GLfloat) height / (GLfloat) width;
+ int y = 0;
+
+ if (width > height * 5) { /* tiny window: show middle */
+ height = width * 9/16;
+ y = -height/2;
+ h = height / (GLfloat) width;
+ }
+
+ glViewport (0, y, width, height);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective (40.0, 1/h, 1.0, 250);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ gluLookAt( 0, 0, 30,
+ 0, 0, 0,
+ 0, 1, 0);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+# ifdef WORDBUBBLES
+ {
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int w = (width < 800 ? 25 : 40);
+ int h = 1000;
+ if (bp->tc)
+ textclient_reshape (bp->tc, w, h, w, h,
+ /* Passing bp->max_lines isn't actually necessary */
+ 0);
+ }
+# endif
+
+}
+
+
+ENTRYPOINT Bool
+robot_handle_event (ModeInfo *mi, XEvent *event)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, bp->user_trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &bp->button_down_p))
+ return True;
+#ifdef DEBUG
+ else if (event->xany.type == KeyPress && debug_p)
+ {
+ KeySym keysym;
+ char c = 0;
+ double n[3] = { 1.0, 0.1, 0.1 };
+ int s = (event->xkey.state & ShiftMask ? 10 : 1);
+
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+
+ if (keysym == XK_Right) bp->debug_x += n[0] * s;
+ else if (keysym == XK_Left) bp->debug_x -= n[0] * s;
+ else if (keysym == XK_Up) bp->debug_y += n[1] * s;
+ else if (keysym == XK_Down) bp->debug_y -= n[1] * s;
+ else if (c == '=' || c == '+') bp->debug_z += n[2] * s;
+ else if (c == '-' || c == '_') bp->debug_z -= n[2] * s;
+ else if (c == ' ') bp->debug_x = bp->debug_y = bp->debug_z = 0;
+ else if (c == '\n' || c == '\r')
+ fprintf (stderr, "%.4f %.4f %.4f\n",
+ bp->debug_x, bp->debug_y, bp->debug_z);
+ else return False;
+ return True;
+ }
+#endif /* DEBUG */
+
+ return False;
+}
+
+
+#ifdef HAVE_TEXTURE
+
+static void
+load_textures (ModeInfo *mi)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ XImage *xi;
+
+ xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual,
+ chromesphere_png, sizeof(chromesphere_png));
+ clear_gl_error();
+
+ glGenTextures (1, &bp->chrome_texture);
+ glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
+ xi->width, xi->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, xi->data);
+ check_gl_error("texture");
+
+ glEnable(GL_TEXTURE_GEN_S);
+ glEnable(GL_TEXTURE_GEN_T);
+ glEnable(GL_TEXTURE_2D);
+}
+
+#endif /* HAVE_TEXTURE */
+
+
+static int unit_gear (ModeInfo *, GLfloat color[4]);
+static int draw_ground (ModeInfo *, GLfloat color[4]);
+static void init_walker (ModeInfo *, walker *);
+
+static void
+parse_color (ModeInfo *mi, char *key, GLfloat color[4])
+{
+ XColor xcolor;
+ char *string = get_string_resource (mi->dpy, key, "RobotColor");
+ if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
+ {
+ fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
+ key, string);
+ exit (1);
+ }
+
+ color[0] = xcolor.red / 65536.0;
+ color[1] = xcolor.green / 65536.0;
+ color[2] = xcolor.blue / 65536.0;
+ color[3] = 1;
+}
+
+
+ENTRYPOINT void
+init_robot (ModeInfo *mi)
+{
+ robot_configuration *bp;
+ int wire = MI_IS_WIREFRAME(mi);
+ int i;
+ MI_INIT (mi, bps);
+
+ bp = &bps[MI_SCREEN(mi)];
+
+ bp->glx_context = init_GL(mi);
+
+ reshape_robot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+ glShadeModel(GL_SMOOTH);
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_CULL_FACE);
+
+ if (!wire)
+ {
+ GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
+/* GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
+ GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
+ GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+ GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
+
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+ glLightfv(GL_LIGHT0, GL_POSITION, pos);
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+# ifdef HAVE_TEXTURE
+ if (!wire && do_texture)
+ load_textures (mi);
+# endif
+
+ bp->user_trackball = gltrackball_init (False);
+
+ bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
+ for (i = 0; i < countof(all_objs); i++)
+ bp->dlists[i] = glGenLists (1);
+
+ for (i = 0; i < countof(all_objs); i++)
+ {
+ const struct gllist *gll = *all_objs[i];
+ char *key = 0;
+ GLfloat spec1[4] = {1.00, 1.00, 1.00, 1.0};
+ GLfloat spec2[4] = {0.40, 0.40, 0.70, 1.0};
+ GLfloat *spec = 0;
+ GLfloat shiny = 20;
+
+ glNewList (bp->dlists[i], GL_COMPILE);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glMatrixMode(GL_TEXTURE);
+ glPushMatrix();
+ glMatrixMode(GL_MODELVIEW);
+
+ glRotatef (-90, 1, 0, 0);
+ glRotatef (180, 0, 0, 1);
+ glScalef (6, 6, 6);
+
+ glBindTexture (GL_TEXTURE_2D, 0);
+
+ switch (i) {
+ case ROBOT_BODY_1:
+ key = "bodyColor";
+ spec = spec1;
+ shiny = 128;
+# ifdef HAVE_TEXTURE
+ if (do_texture)
+ {
+ glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
+ glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+ glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+ }
+# endif
+ break;
+ case ROBOT_DOME:
+ key = "domeColor";
+ spec = spec1;
+ shiny = 128;
+# ifdef HAVE_TEXTURE
+ if (do_texture)
+ {
+ glBindTexture (GL_TEXTURE_2D, bp->chrome_texture);
+ glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+ glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
+ }
+# endif
+ break;
+ case ROBOT_BODY_2:
+ key = "insideColor";
+ spec = spec2;
+ shiny = 128;
+ break;
+ case ROBOT_ARM:
+ key = "armColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_HAND:
+ key = "handColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_LEG:
+ key = "legColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_CRANK:
+ key = "crankColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_ROTATOR:
+ key = "wheelColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_GEAR:
+ key = "gearColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_GEARBOX:
+ key = "gearboxColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case ROBOT_WIREFRAME:
+ key = "wireColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ case GROUND:
+ key = "groundColor";
+ spec = spec2;
+ shiny = 20;
+ break;
+ default:
+ abort();
+ }
+
+ parse_color (mi, key, bp->component_colors[i]);
+
+ glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
+ glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
+
+ switch (i) {
+ case ROBOT_DOME:
+ if (! robot_dome)
+ robot_dome = (struct gllist *) calloc (1, sizeof(*robot_dome));
+ robot_dome->points = unit_dome (32, 32, MI_IS_WIREFRAME(mi));
+ break;
+ case ROBOT_GEAR:
+ if (! robot_gear)
+ robot_gear = (struct gllist *) calloc (1, sizeof(*robot_gear));
+ robot_gear->points = unit_gear (mi, bp->component_colors[i]);
+ break;
+ case GROUND:
+ if (! ground)
+ ground = (struct gllist *) calloc (1, sizeof(*ground));
+ ground->points = draw_ground (mi, bp->component_colors[i]);
+ break;
+ case ROBOT_WIREFRAME:
+ glLineWidth (0.3);
+ renderList (gll, True);
+ break;
+ default:
+ renderList (gll, wire);
+ /* glColor3f (1, 1, 1); renderListNormals (gll, 100, True); */
+ /* glColor3f (1, 1, 0); renderListNormals (gll, 100, False); */
+ break;
+ }
+
+ glMatrixMode(GL_TEXTURE);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+
+ glEndList ();
+ }
+
+# ifdef DEBUG
+ if (debug_p) MI_COUNT(mi) = 1;
+# endif
+
+ bp->nwalkers = MI_COUNT(mi);
+ bp->walkers = (walker *) calloc (bp->nwalkers, sizeof (walker));
+
+ for (i = 0; i < bp->nwalkers; i++)
+ init_walker (mi, &bp->walkers[i]);
+
+ /* Since #0 is the one we track, make sure it doesn't walk too straight.
+ */
+ bp->walkers[0].balance *= 1.5;
+
+# ifdef WORDBUBBLES
+ bp->font_data = load_texture_font (mi->dpy, "labelFont");
+ bp->max_lines = get_integer_resource (mi->dpy, "textLines", "TextLines");
+ bp->tc = textclient_open (MI_DISPLAY (mi));
+
+ parse_color (mi, "textColor", bp->text_color);
+ parse_color (mi, "textBackground", bp->text_bg);
+ parse_color (mi, "textBorderColor", bp->text_bd);
+
+ reshape_robot (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+# endif /* WORDBUBBLES */
+
+# ifdef DEBUG
+ if (!debug_p)
+# endif
+ /* Let's tilt the floor a little. */
+ gltrackball_reset (bp->user_trackball,
+ -0.6 + frand(1.2),
+ -0.6 + frand(0.2));
+}
+
+
+static int
+draw_component (ModeInfo *mi, int i)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
+ bp->component_colors[i]);
+ glCallList (bp->dlists[i]);
+ return (*all_objs[i])->points / 3;
+}
+
+static int
+draw_transparent_component (ModeInfo *mi, int i, GLfloat alpha)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int count = 0;
+
+ if (alpha < 0) return 0;
+ if (alpha > 1) alpha = 1;
+ bp->component_colors[i][3] = alpha;
+
+ if (wire || alpha >= 1)
+ return draw_component (mi, i);
+
+ /* Draw into the depth buffer but not the frame buffer */
+ glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ count += draw_component (mi, i);
+
+ /* Now draw into the frame buffer only where there's already depth */
+ glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glDepthFunc (GL_EQUAL);
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ count += draw_component (mi, i);
+ glDepthFunc (GL_LESS);
+ glDisable (GL_BLEND);
+ return count;
+}
+
+
+
+static int
+draw_arm_half (ModeInfo *mi, walker *f)
+{
+ int count = 0;
+ glPushMatrix();
+ count += draw_transparent_component (mi, ROBOT_ARM, f->body_transparency);
+ glPopMatrix();
+ return count;
+}
+
+static int
+draw_hand_half (ModeInfo *mi, walker *f)
+{
+ int count = 0;
+ glPushMatrix();
+ count += draw_transparent_component (mi, ROBOT_HAND, f->body_transparency);
+ glPopMatrix();
+ return count;
+}
+
+
+/* rotation of arm: 0-360.
+ openness of fingers: 0.0 - 1.0.
+ */
+static int
+draw_arm (ModeInfo *mi, walker *f, GLfloat left_p, GLfloat rot, GLfloat open)
+{
+ int count = 0;
+ GLfloat arm_x = 4766; /* distance from origin to arm axis */
+ GLfloat arm_y = 12212;
+
+ open *= 5.5; /* scale of finger range */
+
+# ifdef DEBUG
+ if (debug_p) rot = 0;
+# endif
+
+ glPushMatrix();
+
+ if (! left_p)
+ glTranslatef (0, 0, arm_x * 2);
+
+ glTranslatef (0, arm_y, -arm_x); /* move to origin */
+ glRotatef (rot, 1, 0, 0);
+ glTranslatef (0, -arm_y, arm_x); /* move back */
+
+ glFrontFace(GL_CCW);
+ count += draw_arm_half (mi, f);
+
+ glScalef (1, -1, 1);
+ glTranslatef (0, -arm_y * 2, 0);
+ glFrontFace(GL_CW);
+ count += draw_arm_half (mi, f);
+
+ glPushMatrix();
+ glTranslatef (0, 0, -arm_x * 2);
+ glScalef (1, 1, -1);
+ glFrontFace(GL_CCW);
+ count += draw_arm_half (mi, f);
+
+ glScalef (1, -1, 1);
+ glTranslatef (0, -arm_y * 2, 0);
+ glFrontFace(GL_CW);
+ count += draw_arm_half (mi, f);
+ glPopMatrix();
+
+ glTranslatef (0, 0, open);
+ glFrontFace(GL_CW);
+ count += draw_hand_half (mi, f);
+
+ glTranslatef (0, 0, -open);
+ glScalef (1, 1, -1);
+ glTranslatef (0, 0, arm_x * 2);
+ glFrontFace(GL_CCW);
+ glTranslatef (0, 0, open);
+ count += draw_hand_half (mi, f);
+
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_body_half (ModeInfo *mi, walker *f, Bool inside_p)
+{
+ int count = 0;
+ int which = (inside_p ? ROBOT_BODY_2 : ROBOT_BODY_1);
+ glPushMatrix();
+ count += draw_transparent_component (mi, which, f->body_transparency);
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_body (ModeInfo *mi, walker *f, Bool inside_p)
+{
+ int count = 0;
+ glPushMatrix();
+
+ glFrontFace(GL_CCW);
+ count += draw_body_half (mi, f, inside_p);
+
+ glScalef (1, 1, -1);
+ glFrontFace(GL_CW);
+ count += draw_body_half (mi, f, inside_p);
+
+ glPopMatrix();
+
+ return count;
+}
+
+
+static int
+draw_gearbox_half (ModeInfo *mi)
+{
+ int count = 0;
+ glPushMatrix();
+ count += draw_component (mi, ROBOT_GEARBOX);
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_gearbox (ModeInfo *mi)
+{
+ int count = 0;
+ glPushMatrix();
+
+ glFrontFace(GL_CCW);
+ count += draw_gearbox_half (mi);
+
+ glScalef (1, 1, -1);
+ glFrontFace(GL_CW);
+ count += draw_gearbox_half (mi);
+
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+unit_gear (ModeInfo *mi, GLfloat color[4])
+{
+ int wire = MI_IS_WIREFRAME(mi);
+ gear G = { 0, };
+ gear *g = &G;
+
+ g->r = 0.5;
+ g->nteeth = 16;
+ g->tooth_h = 0.12;
+ g->thickness = 0.32;
+ g->thickness2 = g->thickness * 0.5;
+ g->thickness3 = g->thickness;
+ g->inner_r = g->r * 0.7;
+ g->inner_r2 = g->r * 0.4;
+ g->inner_r3 = g->r * 0.1;
+ g->size = INVOLUTE_LARGE;
+
+ g->color[0] = g->color2[0] = color[0];
+ g->color[1] = g->color2[1] = color[1];
+ g->color[2] = g->color2[2] = color[2];
+ g->color[3] = g->color2[3] = color[3];
+
+ return draw_involute_gear (g, wire);
+}
+
+
+static int
+draw_gear (ModeInfo *mi)
+{
+ int count = 0;
+ GLfloat n = 350;
+ glScalef (n, n, n);
+ count += draw_component (mi, ROBOT_GEAR);
+ return count;
+}
+
+
+static int
+draw_crank (ModeInfo *mi, walker *f, GLfloat rot)
+{
+ int count = 0;
+ GLfloat origin = 12210.0;
+
+ rot = -rot;
+
+ glPushMatrix();
+
+ glTranslatef (0, origin, 0); /* position at origin */
+ glRotatef (rot, 0, 0, 1);
+
+ glPushMatrix();
+ glRotatef (90, 1, 0, 0);
+ count += draw_gear (mi);
+ glPopMatrix();
+
+ glTranslatef (0, -origin, 0); /* move back */
+
+ glFrontFace(GL_CCW);
+ count += draw_component (mi, ROBOT_CRANK);
+
+ glPopMatrix();
+
+ return count;
+}
+
+
+static int
+draw_rotator_half (ModeInfo *mi)
+{
+ int count = 0;
+ glPushMatrix();
+ count += draw_component (mi, ROBOT_ROTATOR);
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_rotator (ModeInfo *mi, walker *f, GLfloat rot)
+{
+ int count = 0;
+ GLfloat origin = 10093.0;
+
+ glPushMatrix();
+
+ glTranslatef (0, origin, 0); /* position at origin */
+ glRotatef (rot, 0, 0, 1);
+
+ glPushMatrix();
+ glRotatef (90, 1, 0, 0);
+ count += draw_gear (mi);
+ glPopMatrix();
+
+# ifdef DEBUG
+ if (debug_p)
+ {
+ glDisable(GL_LIGHTING);
+ glBegin(GL_LINES);
+ glVertex3f(0, 0, -3000);
+ glVertex3f(0, 0, 3000);
+ glEnd();
+ if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
+ }
+# endif
+
+ glTranslatef (0, -origin, 0); /* move back */
+
+ glFrontFace(GL_CCW);
+ count += draw_rotator_half (mi);
+
+ glScalef (1, 1, -1);
+ glFrontFace(GL_CW);
+ glRotatef (180, 0, 0, 1);
+ glTranslatef (0, -origin * 2, 0); /* move back */
+ count += draw_rotator_half (mi);
+
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_leg_half (ModeInfo *mi)
+{
+ int count = 0;
+ glPushMatrix();
+ count += draw_component (mi, ROBOT_LEG);
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_wireframe (ModeInfo *mi, walker *f)
+{
+ int wire = MI_IS_WIREFRAME(mi);
+ int count = 0;
+ GLfloat alpha = 0.6 - f->body_transparency;
+ if (alpha < 0) return 0;
+ alpha *= 0.3;
+ if (!wire) glDisable (GL_LIGHTING);
+ glPushMatrix();
+ count += draw_transparent_component (mi, ROBOT_WIREFRAME, alpha);
+ glPopMatrix();
+ if (!wire) glEnable (GL_LIGHTING);
+ return count;
+}
+
+
+static int
+draw_leg (ModeInfo *mi, GLfloat rot, Bool left_p)
+{
+ int count = 0;
+ GLfloat x, y;
+ GLfloat leg_distance = 9401; /* distance from ground to leg axis */
+ GLfloat rot_distance = 10110; /* distance from ground to rotator axis */
+ GLfloat pin_distance = 14541; /* distance from ground to stop pin */
+ GLfloat orbit_r = rot_distance - leg_distance; /* radius of rotation */
+
+ /* Actually it's the bottom of the pin minus its diameter, or something. */
+ pin_distance -= 590;
+
+ glPushMatrix();
+
+ if (left_p)
+ glRotatef (180, 0, 1, 0);
+
+ if (!left_p) rot = -(rot + 180);
+
+ rot -= 90;
+
+ x = orbit_r * cos (-rot * M_PI / 180);
+ y = orbit_r * sin (-rot * M_PI / 180);
+
+ {
+ /* Rotate the leg by angle B of the right A
+ triangle ABC, where: /:
+ / :
+ A = position of stop pin / :
+ D = position of rotator wheel's axis , - ~ ~ ~ - ,
+ C = D + y , ' / : ' ,
+ B = D + xy (leg's axis) , / : ,
+ , / : ,
+ So: , / : ,
+ H = dist(A,B) , / D ,
+ O = dist(A,C) , / : ,
+ sin(th) = O/H , / : ,
+ th = asin(O/H) B ~ ~ ~ ~ ~ C .
+ , , '
+ ' - , _ _ _ , '
+ */
+ GLfloat Ay = pin_distance - leg_distance;
+ GLfloat Cx = 0, Cy = y;
+ GLfloat Bx = x;
+ GLfloat dBC = Cx - Bx;
+ GLfloat dAC = Cy - Ay;
+ GLfloat dAB = sqrt (dBC*dBC + dAC*dAC);
+ GLfloat th = asin (dAC / dAB);
+ rot = th / (M_PI / 180.0);
+ rot += 90;
+ if (dBC > 0) rot = 360-rot;
+ }
+
+ glTranslatef (0, orbit_r, 0); /* position on rotator axis */
+ glTranslatef (x, y, 0);
+
+ glTranslatef (0, leg_distance, 0); /* position on leg axis */
+ glRotatef(rot, 0, 0, 1);
+ glTranslatef (0, -leg_distance, 0); /* move back */
+
+ glFrontFace(GL_CCW);
+ count += draw_leg_half (mi);
+
+ glScalef (-1, 1, 1);
+ glFrontFace(GL_CW);
+ count += draw_leg_half (mi);
+
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_dome (ModeInfo *mi, walker *f)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int which = ROBOT_DOME;
+ int count = 0;
+ GLfloat n = 8.3;
+ GLfloat trans = f->body_transparency;
+ GLfloat max = 0.7;
+ GLfloat dome_y = 15290;
+
+ if (trans < 0) trans = 0;
+ if (trans > max) trans = max;
+
+ if (!wire) glEnable (GL_BLEND);
+
+ glPushMatrix();
+ glTranslatef (0, dome_y, 0);
+ glScalef (100, 100, 100);
+ glRotatef (90, 1, 0, 0);
+ glTranslatef (0.35, 0, 0);
+ glScalef (n, n, n);
+ glFrontFace(GL_CCW);
+ bp->component_colors[which][3] = trans;
+ count += draw_component (mi, which);
+ glPopMatrix();
+
+ if (!wire) glDisable (GL_BLEND);
+
+ return count;
+}
+
+
+/* Is this robot overlapping any other?
+ */
+static Bool
+collision_p (ModeInfo *mi, walker *w, GLfloat extra_space)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ if (MI_COUNT(mi) <= 1) return False;
+
+ for (i = 0; i < MI_COUNT(mi); i++)
+ {
+ walker *w2 = &bp->walkers[i];
+ GLfloat min = 0.75 + extra_space;
+ GLfloat d, dx, dy;
+ if (w == w2) continue;
+ dx = w->x - w2->x;
+ dy = w->y - w2->y;
+ d = (dx*dx + dy*dy);
+ if (d <= min*min) return True;
+ }
+ return False;
+}
+
+
+/* I couldn't figure out a straightforward trig solution to the
+ forward/backward tilting that happens as weight passes from one
+ foot to another, so I just built a tool to eyeball it manually.
+ { vertical_translation, rotation, forward_translation }
+ */
+static const struct { GLfloat up, rot, fwd; } wobble_profile[360] = {
+ { 0.0000, 0.00, 0.0000 }, /* 0 */
+ { 0.0000, -0.01, 0.0025 },
+ { 0.0000, -0.25, 0.0040 },
+ { 0.0000, -0.41, 0.0060 },
+ { 0.0000, -0.62, 0.0080 },
+ { 0.0000, -0.80, 0.0095 },
+ { 0.0000, -0.90, 0.0120 },
+ { 0.0000, -1.10, 0.0135 },
+ { 0.0000, -1.25, 0.0150 },
+ { 0.0000, -1.40, 0.0175 },
+ { 0.0000, -1.50, 0.0195 }, /* 10 */
+ { 0.0000, -1.70, 0.0215 },
+ { 0.0000, -1.80, 0.0230 },
+ { 0.0000, -2.00, 0.0250 },
+ { 0.0000, -2.10, 0.0270 },
+ { -0.0005, -2.30, 0.0290 },
+ { -0.0005, -2.50, 0.0305 },
+ { -0.0005, -2.60, 0.0330 },
+ { -0.0005, -2.70, 0.0330 },
+ { -0.0005, -2.90, 0.0350 },
+ { -0.0005, -3.00, 0.0365 }, /* 20 */
+ { -0.0010, -3.20, 0.0380 },
+ { -0.0005, -3.30, 0.0400 },
+ { -0.0010, -3.50, 0.0420 },
+ { -0.0010, -3.70, 0.0440 },
+ { -0.0010, -3.80, 0.0460 },
+ { -0.0010, -3.90, 0.0470 },
+ { -0.0015, -4.10, 0.0500 },
+ { -0.0015, -4.20, 0.0515 },
+ { -0.0015, -4.40, 0.0535 },
+ { -0.0015, -4.50, 0.0550 }, /* 30 */
+ { -0.0015, -4.60, 0.0565 },
+ { -0.0020, -4.80, 0.0585 },
+ { -0.0020, -5.00, 0.0600 },
+ { -0.0020, -5.10, 0.0620 },
+ { -0.0025, -5.20, 0.0635 },
+ { -0.0025, -5.40, 0.0655 },
+ { -0.0025, -5.50, 0.0675 },
+ { -0.0025, -5.60, 0.0690 },
+ { -0.0030, -5.80, 0.0710 },
+ { -0.0030, -5.90, 0.0720 }, /* 40 */
+ { -0.0030, -6.00, 0.0740 },
+ { -0.0035, -6.10, 0.0760 },
+ { -0.0035, -6.30, 0.0790 },
+ { -0.0040, -6.40, 0.0805 },
+ { -0.0040, -6.50, 0.0820 },
+ { -0.0040, -6.60, 0.0835 },
+ { -0.0045, -6.80, 0.0855 },
+ { -0.0045, -6.90, 0.0870 },
+ { -0.0050, -7.00, 0.0885 },
+ { -0.0050, -7.20, 0.0900 }, /* 50 */
+ { -0.0050, -7.20, 0.0915 },
+ { -0.0055, -7.40, 0.0930 },
+ { -0.0055, -7.50, 0.0945 },
+ { -0.0060, -7.60, 0.0960 },
+ { -0.0060, -7.70, 0.0970 },
+ { -0.0065, -7.80, 0.0985 },
+ { -0.0060, -7.70, 0.0995 },
+ { -0.0060, -7.60, 0.1010 },
+ { -0.0060, -7.50, 0.1020 },
+ { -0.0055, -7.30, 0.1030 }, /* 60 */
+ { -0.0050, -7.10, 0.1040 },
+ { -0.0050, -6.90, 0.1050 },
+ { -0.0045, -6.80, 0.1065 },
+ { -0.0045, -6.50, 0.1075 },
+ { -0.0040, -6.40, 0.1085 },
+ { -0.0040, -6.20, 0.1095 },
+ { -0.0040, -6.00, 0.1105 },
+ { -0.0035, -5.80, 0.1115 },
+ { -0.0030, -5.50, 0.1125 },
+ { -0.0030, -5.40, 0.1135 }, /* 70 */
+ { -0.0030, -5.10, 0.1145 },
+ { -0.0030, -4.90, 0.1150 },
+ { -0.0025, -4.70, 0.1160 },
+ { -0.0025, -4.40, 0.1165 },
+ { -0.0025, -4.20, 0.1175 },
+ { -0.0020, -3.90, 0.1180 },
+ { -0.0020, -3.70, 0.1185 },
+ { -0.0020, -3.40, 0.1190 },
+ { -0.0020, -3.10, 0.1195 },
+ { -0.0020, -2.90, 0.1200 }, /* 80 */
+ { -0.0015, -2.60, 0.1200 },
+ { -0.0015, -2.30, 0.1205 },
+ { -0.0015, -2.00, 0.1210 },
+ { -0.0015, -1.80, 0.1215 },
+ { -0.0015, -1.50, 0.1215 },
+ { -0.0015, -1.20, 0.1215 },
+ { -0.0015, -0.90, 0.1215 },
+ { -0.0015, -0.60, 0.1215 },
+ { -0.0015, -0.30, 0.1215 },
+ { -0.0010, 0.00, 0.1215 }, /* 90 */
+ { -0.0010, 0.30, 0.1215 },
+ { -0.0010, 0.60, 0.1215 },
+ { -0.0010, 0.90, 0.1215 },
+ { -0.0010, 1.20, 0.1215 },
+ { -0.0015, 1.40, 0.1215 },
+ { -0.0015, 1.70, 0.1215 },
+ { -0.0015, 2.00, 0.1215 },
+ { -0.0015, 2.30, 0.1215 },
+ { -0.0015, 2.60, 0.1215 },
+ { -0.0015, 2.80, 0.1220 }, /* 100 */
+ { -0.0020, 3.10, 0.1225 },
+ { -0.0020, 3.30, 0.1230 },
+ { -0.0020, 3.60, 0.1235 },
+ { -0.0020, 3.90, 0.1240 },
+ { -0.0025, 4.10, 0.1245 },
+ { -0.0025, 4.40, 0.1250 },
+ { -0.0025, 4.60, 0.1260 },
+ { -0.0025, 4.90, 0.1265 },
+ { -0.0030, 5.10, 0.1275 },
+ { -0.0030, 5.30, 0.1285 }, /* 110 */
+ { -0.0035, 5.60, 0.1290 },
+ { -0.0035, 5.80, 0.1300 },
+ { -0.0035, 6.00, 0.1310 },
+ { -0.0040, 6.20, 0.1325 },
+ { -0.0040, 6.40, 0.1335 },
+ { -0.0045, 6.60, 0.1345 },
+ { -0.0045, 6.70, 0.1355 },
+ { -0.0050, 6.90, 0.1365 },
+ { -0.0050, 7.10, 0.1375 },
+ { -0.0055, 7.30, 0.1390 }, /* 120 */
+ { -0.0055, 7.40, 0.1400 },
+ { -0.0060, 7.50, 0.1415 },
+ { -0.0065, 8.00, 0.1425 },
+ { -0.0065, 7.80, 0.1440 },
+ { -0.0060, 7.80, 0.1455 },
+ { -0.0060, 7.60, 0.1470 },
+ { -0.0055, 7.50, 0.1485 },
+ { -0.0055, 7.40, 0.1500 },
+ { -0.0050, 7.30, 0.1515 },
+ { -0.0050, 7.20, 0.1530 }, /* 130 */
+ { -0.0050, 7.00, 0.1545 },
+ { -0.0045, 6.90, 0.1560 },
+ { -0.0045, 6.80, 0.1575 },
+ { -0.0040, 6.70, 0.1590 },
+ { -0.0040, 6.50, 0.1605 },
+ { -0.0040, 6.40, 0.1625 },
+ { -0.0035, 6.30, 0.1640 },
+ { -0.0035, 6.10, 0.1655 },
+ { -0.0030, 6.10, 0.1670 },
+ { -0.0030, 5.90, 0.1690 }, /* 140 */
+ { -0.0030, 5.70, 0.1705 },
+ { -0.0025, 5.70, 0.1720 },
+ { -0.0025, 5.50, 0.1740 },
+ { -0.0025, 5.40, 0.1755 },
+ { -0.0025, 5.20, 0.1775 },
+ { -0.0020, 5.10, 0.1790 },
+ { -0.0020, 5.00, 0.1810 },
+ { -0.0020, 4.80, 0.1825 },
+ { -0.0015, 4.70, 0.1840 },
+ { -0.0015, 4.60, 0.1860 }, /* 150 */
+ { -0.0015, 4.40, 0.1880 },
+ { -0.0015, 4.20, 0.1900 },
+ { -0.0015, 4.10, 0.1915 },
+ { -0.0010, 4.00, 0.1935 },
+ { -0.0010, 3.80, 0.1955 },
+ { -0.0010, 3.70, 0.1970 },
+ { -0.0010, 3.50, 0.1990 },
+ { -0.0005, 3.40, 0.2010 },
+ { -0.0010, 3.20, 0.2025 },
+ { -0.0005, 3.10, 0.2045 }, /* 160 */
+ { -0.0005, 2.90, 0.2065 },
+ { -0.0005, 2.80, 0.2085 },
+ { -0.0005, 2.60, 0.2105 },
+ { -0.0005, 2.50, 0.2120 },
+ { -0.0005, 2.30, 0.2140 },
+ { -0.0005, 2.20, 0.2160 },
+ { -0.0005, 2.00, 0.2180 },
+ { 0.0000, 1.90, 0.2200 },
+ { 0.0000, 1.70, 0.2220 },
+ { 0.0000, 1.60, 0.2235 }, /* 170 */
+ { 0.0000, 1.40, 0.2255 },
+ { 0.0000, 1.30, 0.2275 },
+ { 0.0000, 1.10, 0.2295 },
+ { 0.0000, 0.90, 0.2315 },
+ { 0.0000, 0.80, 0.2335 },
+ { 0.0000, 0.60, 0.2355 },
+ { 0.0000, 0.50, 0.2375 },
+ { 0.0000, 0.30, 0.2395 },
+ { 0.0000, 0.10, 0.2415 },
+ { 0.0000, 0.00, 0.2430 }, /* 180 */
+ { 0.0000, -0.10, 0.2450 },
+ { 0.0000, -0.30, 0.2470 },
+ { 0.0000, -0.40, 0.2490 },
+ { 0.0000, -0.60, 0.2510 },
+ { 0.0000, -0.80, 0.2530 },
+ { 0.0000, -0.90, 0.2550 },
+ { 0.0000, -1.10, 0.2570 },
+ { 0.0000, -1.20, 0.2590 },
+ { 0.0000, -1.40, 0.2610 },
+ { 0.0000, -1.50, 0.2625 }, /* 190 */
+ { 0.0000, -1.70, 0.2645 },
+ { 0.0000, -1.80, 0.2665 },
+ { -0.0005, -2.00, 0.2685 },
+ { -0.0005, -2.10, 0.2705 },
+ { -0.0005, -2.30, 0.2725 },
+ { -0.0005, -2.40, 0.2740 },
+ { -0.0005, -2.60, 0.2760 },
+ { -0.0005, -2.80, 0.2780 },
+ { -0.0005, -2.90, 0.2800 },
+ { -0.0005, -3.00, 0.2820 }, /* 200 */
+ { -0.0010, -3.20, 0.2835 },
+ { -0.0005, -3.30, 0.2855 },
+ { -0.0010, -3.50, 0.2875 },
+ { -0.0010, -3.70, 0.2895 },
+ { -0.0010, -3.80, 0.2910 },
+ { -0.0010, -3.90, 0.2930 },
+ { -0.0010, -4.00, 0.2950 },
+ { -0.0015, -4.20, 0.2965 },
+ { -0.0015, -4.40, 0.2985 },
+ { -0.0015, -4.50, 0.3000 }, /* 210 */
+ { -0.0015, -4.60, 0.3020 },
+ { -0.0020, -4.80, 0.3040 },
+ { -0.0020, -5.00, 0.3055 },
+ { -0.0020, -5.00, 0.3075 },
+ { -0.0025, -5.20, 0.3090 },
+ { -0.0025, -5.30, 0.3110 },
+ { -0.0025, -5.50, 0.3125 },
+ { -0.0025, -5.60, 0.3140 },
+ { -0.0030, -5.70, 0.3160 },
+ { -0.0030, -5.90, 0.3175 }, /* 220 */
+ { -0.0030, -6.00, 0.3190 },
+ { -0.0035, -6.10, 0.3210 },
+ { -0.0035, -6.30, 0.3225 },
+ { -0.0040, -6.40, 0.3240 },
+ { -0.0040, -6.50, 0.3255 },
+ { -0.0040, -6.60, 0.3270 },
+ { -0.0045, -6.80, 0.3290 },
+ { -0.0045, -6.90, 0.3305 },
+ { -0.0050, -7.00, 0.3320 },
+ { -0.0050, -7.20, 0.3335 }, /* 230 */
+ { -0.0050, -7.20, 0.3350 },
+ { -0.0055, -7.40, 0.3365 },
+ { -0.0055, -7.50, 0.3380 },
+ { -0.0060, -7.60, 0.3390 },
+ { -0.0060, -7.70, 0.3405 },
+ { -0.0065, -7.80, 0.3420 },
+ { -0.0060, -7.60, 0.3425 },
+ { -0.0060, -7.50, 0.3440 },
+ { -0.0055, -7.40, 0.3455 },
+ { -0.0055, -7.20, 0.3470 }, /* 240 */
+ { -0.0050, -7.10, 0.3480 },
+ { -0.0050, -6.90, 0.3490 },
+ { -0.0045, -6.80, 0.3500 },
+ { -0.0045, -6.50, 0.3510 },
+ { -0.0040, -6.40, 0.3520 },
+ { -0.0040, -6.10, 0.3535 },
+ { -0.0035, -6.00, 0.3545 },
+ { -0.0035, -5.80, 0.3550 },
+ { -0.0030, -5.50, 0.3560 },
+ { -0.0030, -5.30, 0.3570 }, /* 250 */
+ { -0.0030, -5.10, 0.3580 },
+ { -0.0030, -4.90, 0.3585 },
+ { -0.0025, -4.70, 0.3595 },
+ { -0.0025, -4.40, 0.3600 },
+ { -0.0020, -4.10, 0.3610 },
+ { -0.0020, -3.90, 0.3615 },
+ { -0.0020, -3.70, 0.3620 },
+ { -0.0020, -3.30, 0.3625 },
+ { -0.0020, -3.10, 0.3630 },
+ { -0.0015, -2.80, 0.3635 }, /* 260 */
+ { -0.0015, -2.60, 0.3640 },
+ { -0.0015, -2.40, 0.3645 },
+ { -0.0015, -2.00, 0.3645 },
+ { -0.0015, -1.80, 0.3650 },
+ { -0.0015, -1.40, 0.3650 },
+ { -0.0015, -1.20, 0.3655 },
+ { -0.0010, -0.90, 0.3655 },
+ { -0.0010, -0.60, 0.3655 },
+ { -0.0010, -0.30, 0.3655 },
+ { -0.0010, 0.00, 0.3655 }, /* 270 */
+ { -0.0010, 0.30, 0.3655 },
+ { -0.0010, 0.60, 0.3655 },
+ { -0.0010, 0.90, 0.3655 },
+ { -0.0015, 1.10, 0.3655 },
+ { -0.0015, 1.40, 0.3655 },
+ { -0.0015, 1.70, 0.3655 },
+ { -0.0015, 1.90, 0.3660 },
+ { -0.0015, 2.20, 0.3660 },
+ { -0.0015, 2.50, 0.3665 },
+ { -0.0015, 2.80, 0.3670 }, /* 280 */
+ { -0.0015, 3.10, 0.3675 },
+ { -0.0020, 3.40, 0.3680 },
+ { -0.0020, 3.70, 0.3685 },
+ { -0.0020, 3.90, 0.3690 },
+ { -0.0025, 4.10, 0.3695 },
+ { -0.0025, 4.40, 0.3700 },
+ { -0.0025, 4.60, 0.3710 },
+ { -0.0025, 4.80, 0.3715 },
+ { -0.0025, 5.00, 0.3730 },
+ { -0.0030, 5.40, 0.3735 }, /* 290 */
+ { -0.0035, 5.60, 0.3745 },
+ { -0.0035, 5.80, 0.3755 },
+ { -0.0035, 6.00, 0.3765 },
+ { -0.0040, 6.20, 0.3775 },
+ { -0.0045, 6.50, 0.3785 },
+ { -0.0045, 6.60, 0.3795 },
+ { -0.0045, 6.80, 0.3805 },
+ { -0.0050, 7.00, 0.3815 },
+ { -0.0050, 7.10, 0.3825 },
+ { -0.0055, 7.20, 0.3840 }, /* 300 */
+ { -0.0055, 7.40, 0.3850 },
+ { -0.0060, 7.50, 0.3865 },
+ { -0.0060, 7.70, 0.3875 },
+ { -0.0065, 7.80, 0.3890 },
+ { -0.0060, 7.80, 0.3900 },
+ { -0.0060, 7.60, 0.3915 },
+ { -0.0055, 7.60, 0.3930 },
+ { -0.0055, 7.40, 0.3945 },
+ { -0.0050, 7.30, 0.3960 },
+ { -0.0050, 7.20, 0.3975 }, /* 310 */
+ { -0.0050, 7.00, 0.3990 },
+ { -0.0045, 6.90, 0.4005 },
+ { -0.0045, 6.80, 0.4020 },
+ { -0.0040, 6.70, 0.4035 },
+ { -0.0040, 6.60, 0.4050 },
+ { -0.0040, 6.40, 0.4065 },
+ { -0.0035, 6.30, 0.4085 },
+ { -0.0035, 6.20, 0.4100 },
+ { -0.0030, 6.10, 0.4115 },
+ { -0.0030, 5.90, 0.4130 }, /* 320 */
+ { -0.0030, 5.80, 0.4150 },
+ { -0.0025, 5.70, 0.4165 },
+ { -0.0025, 5.50, 0.4180 },
+ { -0.0025, 5.40, 0.4200 },
+ { -0.0025, 5.20, 0.4215 },
+ { -0.0020, 5.10, 0.4235 },
+ { -0.0020, 5.00, 0.4250 },
+ { -0.0020, 4.80, 0.4270 },
+ { -0.0015, 4.70, 0.4285 },
+ { -0.0015, 4.60, 0.4305 }, /* 330 */
+ { -0.0015, 4.40, 0.4325 },
+ { -0.0015, 4.20, 0.4340 },
+ { -0.0015, 4.10, 0.4360 },
+ { -0.0010, 4.00, 0.4375 },
+ { -0.0010, 3.80, 0.4395 },
+ { -0.0010, 3.70, 0.4415 },
+ { -0.0010, 3.50, 0.4435 },
+ { -0.0005, 3.40, 0.4450 },
+ { -0.0010, 3.20, 0.4470 },
+ { -0.0005, 3.10, 0.4490 }, /* 340 */
+ { -0.0005, 2.90, 0.4510 },
+ { -0.0005, 2.80, 0.4525 },
+ { -0.0005, 2.60, 0.4545 },
+ { -0.0005, 2.40, 0.4565 },
+ { -0.0005, 2.30, 0.4585 },
+ { -0.0005, 2.20, 0.4605 },
+ { -0.0005, 2.00, 0.4620 },
+ { 0.0000, 1.90, 0.4640 },
+ { 0.0000, 1.70, 0.4660 },
+ { 0.0000, 1.60, 0.4680 }, /* 350 */
+ { 0.0000, 1.40, 0.4700 },
+ { 0.0000, 1.20, 0.4720 },
+ { 0.0000, 1.10, 0.4740 },
+ { 0.0000, 0.90, 0.4760 },
+ { 0.0000, 0.80, 0.4780 },
+ { 0.0000, 0.60, 0.4795 },
+ { 0.0000, 0.50, 0.4815 },
+ { 0.0000, 0.30, 0.4835 },
+ { 0.0000, 0.20, 0.4855 }, /* 359 */
+};
+
+
+/* Turn the crank by 1 degree, which moves the legs and displaces the robot.
+ */
+static void
+tick_walker (ModeInfo *mi, walker *f)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int deg;
+ GLfloat th, fwd;
+
+ if (bp->button_down_p) return;
+
+ f->crank_rot++;
+ deg = ((int) (f->crank_rot + 0.5)) % 360;
+
+# ifdef DEBUG
+ if (debug_p)
+ {
+ f->crank_rot = bp->debug_x;
+ f->pitch = wobble_profile[deg].rot;
+ f->z = wobble_profile[deg].up;
+ }
+ else
+# endif /* DEBUG */
+
+ if (deg == 0)
+ {
+ fwd = wobble_profile[deg].fwd;
+ f->pitch = wobble_profile[deg].rot;
+ f->z = wobble_profile[deg].up;
+ }
+ else
+ {
+ fwd = (wobble_profile[deg].fwd - wobble_profile[deg-1].fwd);
+ f->pitch += (wobble_profile[deg].rot - wobble_profile[deg-1].rot);
+ f->z += (wobble_profile[deg].up - wobble_profile[deg-1].up);
+ }
+
+ /* Lean slightly toward the foot that is raised off the ground. */
+ f->roll = -2.5 * sin ((deg - 90) * M_PI / 180);
+
+ if (!(random() % 10))
+ {
+ GLfloat b = f->balance / 10.0;
+ int s = (f->balance > 0 ? 1 : -1);
+ if (s < 0) b = -b;
+ f->facing += s * frand (b);
+ }
+
+# ifdef DEBUG
+ if (debug_p) fwd = 0;
+# endif
+
+ {
+ GLfloat ox = f->x;
+ GLfloat oy = f->y;
+ th = f->facing * M_PI / 180.0;
+ f->x += fwd * cos (th);
+ f->y += fwd * sin (th);
+
+ /* If moving this robot would collide with another, undo the move,
+ recoil, and randomly turn.
+ */
+ if (collision_p (mi, f, 0))
+ {
+ fwd *= -1.5;
+ f->x = ox + fwd * cos (th);
+ f->y = oy + fwd * sin (th);
+ f->facing += frand(10) - 5;
+ if (! random() % 30)
+ f->facing += frand(90) - 45;
+ }
+ }
+
+
+ if (!do_fade ||
+ opacity > 0.5) /* Don't bother fading if it's already transparent. */
+ {
+ GLfloat tick = 0.002;
+ GLfloat linger = 3;
+
+ /* If we're not fading, maybe start fading out. */
+ if (f->fading_p == 0 && ! (random() % 40000))
+ f->fading_p = -1;
+
+# ifdef DEBUG
+ if (debug_p) f->fading_p = 0;
+# endif
+
+ if (f->fading_p < 0)
+ {
+ f->body_transparency -= tick;
+ if (f->body_transparency <= -linger)
+ {
+ f->body_transparency = -linger;
+ f->fading_p = 1;
+ }
+ }
+ else if (f->fading_p > 0)
+ {
+ f->body_transparency += tick;
+ if (f->body_transparency >= opacity)
+ {
+ f->body_transparency = opacity;
+ f->fading_p = 0;
+ }
+ }
+ }
+}
+
+
+static void
+init_walker (ModeInfo *mi, walker *f)
+{
+ int i, start_tick = random() % 360;
+
+ f->crank_rot = 0;
+ f->pitch = wobble_profile[0].rot;
+ f->z = wobble_profile[0].up;
+
+ f->body_transparency = opacity;
+
+ f->hand_rot[0] = frand(180);
+ f->hand_pos[0] = 0.6 + frand(0.4);
+ f->hand_rot[1] = 180 - f->hand_rot[0];
+ f->hand_pos[1] = f->hand_pos[0];
+
+ if (! (random() % 30)) f->hand_rot[1] += frand(10);
+ if (! (random() % 30)) f->hand_pos[1] = 0.6 + frand(0.4);
+
+ f->facing = frand(360);
+ f->balance = frand(10) - 5;
+
+ if (MI_COUNT(mi) == 1)
+ f->speed = 1.0;
+ else
+ f->speed = 0.6 + frand(0.8);
+
+# ifdef DEBUG
+ if (debug_p)
+ {
+ start_tick = 0;
+ f->facing = 0;
+ f->balance = 0;
+ }
+# endif
+
+ for (i = 0; i < start_tick; i++)
+ tick_walker (mi, f);
+
+ /* Place them randomly, but non-overlapping. */
+ for (i = 0; i < 1000; i++)
+ {
+ GLfloat range = 10;
+ if (MI_COUNT(mi) > 10) range += MI_COUNT(mi) / 10.0;
+ f->x = frand(range) - range/2;
+ f->y = frand(range) - range/2;
+ if (! collision_p (mi, f, 1.5))
+ break;
+ }
+
+# ifdef DEBUG
+ if (debug_p) f->x = f->y = 0;
+# endif
+
+}
+
+
+/* Draw a robot standing in the right place, 1 unit tall.
+ */
+static int
+draw_walker (ModeInfo *mi, walker *f, const char *tag)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int count = 0;
+ glPushMatrix();
+
+ glTranslatef (f->y, f->z, f->x);
+
+ {
+ GLfloat n = 0.01;
+ glScalef (n, n, n);
+ }
+
+ glRotatef (90, 0, 1, 0);
+ glRotatef (f->facing, 0, 1, 0);
+ glRotatef (f->pitch, 0, 0, 1);
+ glRotatef (f->roll, 1, 0, 0);
+
+ {
+ GLfloat n = 0.00484; /* make it 1 unit tall */
+ glScalef (n, n, n);
+ }
+
+ count += draw_gearbox (mi);
+ count += draw_crank (mi, f, f->crank_rot);
+ count += draw_rotator (mi, f, f->crank_rot);
+ count += draw_leg (mi, f->crank_rot, False);
+ count += draw_leg (mi, f->crank_rot, True);
+ count += draw_wireframe (mi, f);
+
+ /* Draw these last, and outer shell first, to make transparency work.
+ The order in which things hit the depth buffer matters.
+ */
+ if (f->body_transparency >= 0.001)
+ {
+ count += draw_arm (mi, f, True, f->hand_rot[0], f->hand_pos[0]);
+ count += draw_arm (mi, f, False, f->hand_rot[1], f->hand_pos[1]);
+ count += draw_body (mi, f, False);
+ count += draw_body (mi, f, True);
+ count += draw_dome (mi, f);
+ }
+
+ if (tag) /* For debugging depth sorting: label each robot */
+ {
+ GLfloat m[4][4];
+ if (! wire) glDisable (GL_DEPTH_TEST);
+ glColor3f (1, 1, 1);
+ glPushMatrix();
+
+ /* Billboard rotation */
+ glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
+ m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
+ m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
+ m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
+ glLoadIdentity();
+ glMultMatrixf (&m[0][0]);
+ glScalef (0.04, 0.04, 0.04);
+
+ print_texture_string (bp->font_data, tag);
+ glPopMatrix();
+ if (! wire) glEnable (GL_DEPTH_TEST);
+ }
+
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_ground (ModeInfo *mi, GLfloat color[4])
+{
+ int wire = MI_IS_WIREFRAME(mi);
+ GLfloat i, j, k;
+
+ /* When using fog, iOS apparently doesn't like lines or quads that are
+ really long, and extend very far outside of the scene. Maybe? If the
+ length of the line (cells * cell_size) is greater than 25 or so, lines
+ that are oriented steeply away from the viewer tend to disappear
+ (whether implemented as GL_LINES or as GL_QUADS).
+
+ So we do a bunch of smaller grids instead of one big one.
+ */
+ int cells = 30;
+ GLfloat cell_size = 0.8;
+ int points = 0;
+ int grids = 12;
+
+# ifdef DEBUG
+ if (debug_p) return 0;
+# endif
+
+ glPushMatrix();
+
+ glRotatef (frand(90), 0, 0, 1);
+
+ if (!wire)
+ {
+ GLfloat fog_color[4] = { 0, 0, 0, 1 };
+
+ glLineWidth (4);
+ glEnable (GL_LINE_SMOOTH);
+ glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_BLEND);
+
+ glFogi (GL_FOG_MODE, GL_EXP2);
+ glFogfv (GL_FOG_COLOR, fog_color);
+ glFogf (GL_FOG_DENSITY, 0.015);
+ glFogf (GL_FOG_START, -cells/2 * cell_size * grids);
+ glEnable (GL_FOG);
+ }
+
+ glColor4fv (color);
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
+
+ glTranslatef (-cells * grids * cell_size / 2,
+ -cells * grids * cell_size / 2, 0);
+
+ for (j = 0; j < grids; j++)
+ {
+ glPushMatrix();
+ for (k = 0; k < grids; k++)
+ {
+ glBegin (GL_LINES);
+ for (i = -cells/2; i < cells/2; i++)
+ {
+ GLfloat a = i * cell_size;
+ GLfloat b = cells/2 * cell_size;
+ glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++;
+ glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++;
+ }
+ glEnd();
+ glTranslatef (cells * cell_size, 0, 0);
+ }
+ glPopMatrix();
+ glTranslatef (0, cells * cell_size, 0);
+ }
+
+ if (!wire)
+ {
+ glDisable (GL_LINE_SMOOTH);
+ glDisable (GL_BLEND);
+ glDisable (GL_FOG);
+ }
+
+ glPopMatrix();
+
+ return points;
+}
+
+
+/* If the target robot (robot #0) has moved too far from the point at which
+ the camera is aimed, then initiate an animation to move the observer to
+ the new spot.
+
+ Because of the jerky forward motion of the robots, just always focusing
+ on the center of the robot looks terrible, so instead we let them walk
+ a little out of the center of the frame, and then catch up.
+ */
+static void
+look_at_center (ModeInfo *mi)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ GLfloat target_x = bp->walkers[0].x;
+ GLfloat target_y = bp->walkers[0].y;
+ GLfloat target_z = 0.8; /* Look a little bit above his head */
+ GLfloat max_dist = 2.5 / size;
+
+# ifdef DEBUG
+ if (debug_p) return;
+# endif
+
+ if (max_dist < 1) max_dist = 1;
+ if (max_dist > 10) max_dist = 10;
+
+ if (bp->camera_tracking_p)
+ {
+ GLfloat r = (1 - cos (bp->tracking_ratio * M_PI)) / 2;
+ bp->looking_x = bp->olooking_x + r * (target_x - bp->olooking_x);
+ bp->looking_y = bp->olooking_y + r * (target_y - bp->olooking_y);
+ bp->looking_z = bp->olooking_z + r * (target_z - bp->olooking_z);
+
+ bp->tracking_ratio += 0.02;
+ if (bp->tracking_ratio >= 1)
+ {
+ bp->camera_tracking_p = False;
+ bp->olooking_x = bp->looking_x;
+ bp->olooking_y = bp->looking_y;
+ bp->olooking_z = bp->looking_z;
+ }
+ }
+
+ if (! bp->camera_tracking_p)
+ {
+ GLfloat dist =
+ sqrt ((target_x - bp->looking_x) * (target_x - bp->looking_x) +
+ (target_y - bp->looking_y) * (target_y - bp->looking_y) +
+ (target_z - bp->looking_z) * (target_z - bp->looking_z));
+
+ if (dist > max_dist)
+ {
+ bp->camera_tracking_p = True;
+ bp->tracking_ratio = 0;
+ bp->olooking_x = bp->looking_x;
+ bp->olooking_y = bp->looking_y;
+ bp->olooking_z = bp->looking_z;
+ }
+ }
+
+ glTranslatef (-bp->looking_y, -bp->looking_z, -bp->looking_x);
+
+# if 0 /* DEBUG */
+ {
+ GLfloat th;
+ glPushMatrix();
+ glColor3f(1, 0, 0);
+ glTranslatef (target_y, target_z, target_x);
+ glBegin(GL_LINES);
+ glVertex3f(0, -target_z, 0);
+ glVertex3f(0, 1, 0);
+ glVertex3f(-0.1, 0, -0.1);
+ glVertex3f( 0.1, 0, 0.1);
+ glVertex3f(-0.1, 0, 0.1);
+ glVertex3f( 0.1, 0, -0.1);
+ glEnd();
+ glPopMatrix();
+
+ glPushMatrix();
+ glColor3f(0, 1, 0);
+ glTranslatef (bp->looking_y, bp->looking_z, bp->looking_x);
+ glRotatef (30, 0, 1, 0);
+ glBegin(GL_LINES);
+ glVertex3f(0, -bp->looking_z, 0);
+ glVertex3f(0, 1, 0);
+ glVertex3f(-0.1, 0, -0.1);
+ glVertex3f( 0.1, 0, 0.1);
+ glVertex3f(-0.1, 0, 0.1);
+ glVertex3f( 0.1, 0, -0.1);
+ glEnd();
+ glPopMatrix();
+
+ glPushMatrix();
+ glColor3f(0, 0, 1);
+ glTranslatef (bp->olooking_y, bp->olooking_z, bp->olooking_x);
+ glRotatef (60, 0, 1, 0);
+ glBegin(GL_LINES);
+ glVertex3f(0, -bp->olooking_z, 0);
+ glVertex3f(0, 1, 0);
+ glVertex3f(-0.1, 0, -0.1);
+ glVertex3f( 0.1, 0, 0.1);
+ glVertex3f(-0.1, 0, 0.1);
+ glVertex3f( 0.1, 0, -0.1);
+ glEnd();
+
+ glTranslatef (0, -bp->olooking_z, 0);
+ glBegin (GL_LINE_LOOP);
+ for (th = 0; th < M_PI * 2; th += 0.1)
+ glVertex3f (bp->olooking_y + max_dist * cos(th), 0,
+ bp->olooking_x + max_dist * sin(th));
+ glEnd();
+ glPopMatrix();
+ }
+# endif /* DEBUG */
+}
+
+
+#ifdef WORDBUBBLES
+
+/* Draw a cartoony word bubble.
+ W and H are the inside size, for text.
+ Origin is at bottom left.
+ The bubble frame and arrow are outside that.
+ */
+static void
+draw_bubble_box (ModeInfo *mi,
+ GLfloat width, GLfloat height,
+ GLfloat corner_radius,
+ GLfloat arrow_h, GLfloat arrow_x,
+ GLfloat fg[4], GLfloat bg[4])
+{
+
+# define CORNER_POINTS 16
+ GLfloat outline_points[ (CORNER_POINTS + 2) * 4 + 8 ][3];
+ int i = 0;
+ GLfloat th;
+ GLfloat tick = M_PI / 2 / CORNER_POINTS;
+
+ GLfloat arrow_w = arrow_h / 2;
+ GLfloat arrow_x2 = MAX(0, MIN(width - arrow_w, arrow_x));
+
+ GLfloat w2 = MAX(arrow_w, width - corner_radius * 1.10);
+ GLfloat h2 = MAX(0, height - corner_radius * 1.28);
+ GLfloat x2 = (width - w2) / 2;
+ GLfloat y2 = (height - h2) / 2;
+ /* A B C D */
+ GLfloat xa = x2 -corner_radius; /* E _------------_ */
+ GLfloat xb = x2; /* D /__| |__\ */
+ GLfloat xc = xb + w2; /* | | | | */
+ GLfloat xd = xc + corner_radius; /* C |__| EF |__| */
+ GLfloat xe = xb + arrow_x2; /* B \_|_________|_/ */
+ GLfloat xf = xe + arrow_w; /* A \| */
+
+ GLfloat ya = y2 - (corner_radius + arrow_h);
+ GLfloat yb = y2 - corner_radius;
+ GLfloat yc = y2;
+ GLfloat yd = yc + h2;
+ GLfloat ye = yd + corner_radius;
+
+ GLfloat z = 0;
+
+ /* Let the lines take precedence over the fills. */
+ glEnable (GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset (1.0, 1.0);
+
+ glColor4fv (bg);
+ glFrontFace(GL_CW);
+
+ /* top left corner */
+
+ glBegin (GL_TRIANGLE_FAN);
+ glVertex3f (xb, yd, 0);
+ for (th = 0; th < M_PI/2 + tick; th += tick)
+ {
+ GLfloat x = xb - corner_radius * cos(th);
+ GLfloat y = yd + corner_radius * sin(th);
+ glVertex3f (x, y, z);
+ outline_points[i][0] = x;
+ outline_points[i][1] = y;
+ outline_points[i][2] = z;
+ i++;
+ }
+ glEnd();
+
+ /* top edge */
+ outline_points[i][0] = xc;
+ outline_points[i][1] = ye;
+ outline_points[i][2] = z;
+ i++;
+
+ /* top right corner */
+
+ glBegin (GL_TRIANGLE_FAN);
+ glVertex3f (xc, yd, 0);
+ for (th = M_PI/2; th > -tick; th -= tick)
+ {
+ GLfloat x = xc + corner_radius * cos(th);
+ GLfloat y = yd + corner_radius * sin(th);
+ glVertex3f (x, y, z);
+ outline_points[i][0] = x;
+ outline_points[i][1] = y;
+ outline_points[i][2] = z;
+ i++;
+ }
+ glEnd();
+
+ /* right edge */
+ outline_points[i][0] = xd;
+ outline_points[i][1] = yc;
+ outline_points[i][2] = z;
+ i++;
+
+ /* bottom right corner */
+
+ glBegin (GL_TRIANGLE_FAN);
+ glVertex3f (xc, yc, 0);
+ for (th = 0; th < M_PI/2 + tick; th += tick)
+ {
+ GLfloat x = xc + corner_radius * cos(th);
+ GLfloat y = yc - corner_radius * sin(th);
+ glVertex3f (x, y, z);
+ outline_points[i][0] = x;
+ outline_points[i][1] = y;
+ outline_points[i][2] = z;
+ i++;
+ }
+ glEnd();
+
+ /* bottom right edge */
+ outline_points[i][0] = xf;
+ outline_points[i][1] = yb;
+ outline_points[i][2] = z;
+ i++;
+
+ /* arrow triangle */
+ glFrontFace(GL_CW);
+ glBegin (GL_TRIANGLES);
+
+ /* bottom arrow point */
+ outline_points[i][0] = xf;
+ outline_points[i][1] = yb;
+ outline_points[i][2] = z;
+ glVertex3f (outline_points[i][0],
+ outline_points[i][1],
+ outline_points[i][2]);
+ i++;
+
+ /* bottom right edge */
+ outline_points[i][0] = xf;
+ outline_points[i][1] = ya;
+ outline_points[i][2] = z;
+ glVertex3f (outline_points[i][0],
+ outline_points[i][1],
+ outline_points[i][2]);
+ i++;
+
+ outline_points[i][0] = xe;
+ outline_points[i][1] = yb;
+ outline_points[i][2] = z;
+ glVertex3f (outline_points[i][0],
+ outline_points[i][1],
+ outline_points[i][2]);
+ i++;
+ glEnd();
+
+
+ /* bottom left corner */
+
+ glBegin (GL_TRIANGLE_FAN);
+ glVertex3f (xb, yc, 0);
+ for (th = M_PI/2; th > -tick; th -= tick)
+ {
+ GLfloat x = xb - corner_radius * cos(th);
+ GLfloat y = yc - corner_radius * sin(th);
+ glVertex3f (x, y, z);
+ outline_points[i][0] = x;
+ outline_points[i][1] = y;
+ outline_points[i][2] = z;
+ i++;
+ }
+ glEnd();
+
+ glFrontFace(GL_CCW);
+
+ /* left edge */
+ outline_points[i][0] = xa;
+ outline_points[i][1] = yd;
+ outline_points[i][2] = z;
+ i++;
+
+ glFrontFace(GL_CW);
+ glBegin (GL_QUADS);
+ /* left box */
+ glVertex3f (xa, yd, z);
+ glVertex3f (xb, yd, z);
+ glVertex3f (xb, yc, z);
+ glVertex3f (xa, yc, z);
+
+ /* center box */
+ glVertex3f (xb, ye, z);
+ glVertex3f (xc, ye, z);
+ glVertex3f (xc, yb, z);
+ glVertex3f (xb, yb, z);
+
+ /* right box */
+ glVertex3f (xc, yd, z);
+ glVertex3f (xd, yd, z);
+ glVertex3f (xd, yc, z);
+ glVertex3f (xc, yc, z);
+
+ glEnd();
+
+ glLineWidth (2.8);
+ glColor4fv (fg);
+
+ glBegin (GL_LINE_LOOP);
+ while (i > 0)
+ glVertex3fv (outline_points[--i]);
+ glEnd();
+
+ glDisable (GL_POLYGON_OFFSET_FILL);
+}
+
+
+static void
+draw_label (ModeInfo *mi, walker *f, GLfloat y_off, GLfloat scale,
+ const char *label)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ GLfloat m[4][4];
+
+ if (scale == 0) return;
+
+ if (!wire)
+ glDisable (GL_LIGHTING); /* don't light fonts */
+
+ glPushMatrix();
+
+ /* First, we translate the origin to the center of the robot.
+
+ Then we retrieve the prevailing modelview matrix, which
+ includes any rotation, wandering, and user-trackball-rolling
+ of the scene.
+
+ We set the top 3x3 cells of that matrix to be the identity
+ matrix. This removes all rotation from the matrix, while
+ leaving the translation alone. This has the effect of
+ leaving the prevailing coordinate system perpendicular to
+ the camera view: were we to draw a square face, it would
+ be in the plane of the screen.
+
+ Now we translate by `size' toward the viewer -- so that the
+ origin is *just in front* of the ball.
+
+ Then we draw the label text, allowing the depth buffer to
+ do its work: that way, labels on atoms will be occluded
+ properly when other atoms move in front of them.
+
+ This technique (of neutralizing rotation relative to the
+ observer, after both rotations and translations have been
+ applied) is known as "billboarding".
+ */
+
+ if (f)
+ glTranslatef(f->y, 0, f->x); /* get matrix */
+
+ glTranslatef (0, y_off, 0);
+
+ glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]); /* load rot. identity */
+ m[0][0] = 1; m[1][0] = 0; m[2][0] = 0;
+ m[0][1] = 0; m[1][1] = 1; m[2][1] = 0;
+ m[0][2] = 0; m[1][2] = 0; m[2][2] = 1;
+ glLoadIdentity(); /* reset modelview */
+ glMultMatrixf (&m[0][0]); /* replace with ours */
+
+ glTranslatef (0, 0, 0.1); /* move toward camera */
+
+ glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
+
+ {
+ XCharStruct e;
+ int cw, ch, w, h, ascent, descent;
+ GLfloat s;
+ GLfloat max = 24; /* max point size to avoid pixellated text */
+
+ /* Let the font be much larger on iPhone */
+ if (mi->xgwa.height <= 640 || mi->xgwa.width <= 640)
+ max *= 3;
+
+ texture_string_metrics (bp->font_data, "X", &e, &ascent, &descent);
+ cw = e.width;
+ ch = ascent + descent;
+ s = 1.0 / ch;
+ if (ch > max) s *= max/ch;
+
+ s *= scale;
+
+ texture_string_metrics (bp->font_data, label, &e, 0, 0);
+ w = e.width;
+ h = e.ascent + e.descent;
+
+ glScalef (s, s, 1);
+ glTranslatef (-w/2, h*2/3 + (cw * 7), 0);
+
+ glPushMatrix();
+ glTranslatef (0, -h + (ch * 1.2), -0.1);
+ draw_bubble_box (mi, w, h,
+ ch * 2, /* corner radius */
+ ch * 2.5, /* arrow height */
+ w / 2 - cw * 8, /* arrow x */
+ bp->text_bd, bp->text_bg);
+ glPopMatrix();
+
+ glColor4fv (bp->text_color);
+ glTranslatef (0, ch/2, 0);
+ print_texture_string (bp->font_data, label);
+ }
+
+ glPopMatrix();
+
+ /* More efficient to always call glEnable() with correct values
+ than to call glPushAttrib()/glPopAttrib(), since reading
+ attributes from GL does a round-trip and stalls the pipeline.
+ */
+ if (!wire)
+ glEnable (GL_LIGHTING);
+}
+
+
+static void
+fill_words (ModeInfo *mi)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ char *p = bp->words + strlen(bp->words);
+ char *c;
+ int lines = 0;
+ int max = bp->max_lines;
+
+ /* Fewer lines on iPhone */
+ if ((mi->xgwa.height <= 640 || mi->xgwa.width <= 640) &&
+ max > 4)
+ max = 4;
+
+ for (c = bp->words; c < p; c++)
+ if (*c == '\n')
+ lines++;
+
+ while (p < bp->words + sizeof(bp->words) - 1 &&
+ lines < max)
+ {
+ int c = textclient_getc (bp->tc);
+ if (c == '\n')
+ lines++;
+ if (c > 0)
+ *p++ = (char) c;
+ else
+ break;
+ }
+ *p = 0;
+
+ bp->lines = lines;
+}
+
+
+static void
+bubble (ModeInfo *mi)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ int duration = 200;
+ GLfloat fade = 0.015;
+ int chance = (talk_chance <= 0.0 ? 0 :
+ talk_chance >= 0.99 ? 1 :
+ (1-talk_chance) * 1000);
+ GLfloat scale;
+ char *s0 = strdup (bp->words);
+ char *s = s0;
+ int L;
+
+ while (*s == '\n') s++;
+ L = strlen(s);
+ while (L > 0 && (s[L-1] == '\n' || s[L-1] == ' ' || s[L-1] == '\t'))
+ s[--L] = 0;
+ if (! *s) goto DONE;
+
+# ifdef DEBUG
+ if (debug_p) goto DONE;
+# endif
+
+ if (chance <= 0) goto DONE;
+
+ if (bp->bubble_tick > 0)
+ {
+ bp->bubble_tick--;
+ if (! bp->bubble_tick)
+ *bp->words = 0;
+ }
+
+ if (! bp->bubble_tick)
+ {
+ if (!(random() % chance))
+ bp->bubble_tick = duration;
+ else
+ goto DONE;
+ }
+
+ scale = (bp->bubble_tick < duration * fade
+ ? bp->bubble_tick / (duration * fade)
+ : (bp->bubble_tick > duration * (1 - fade)
+ ? 1 - ((bp->bubble_tick - duration * (1 - fade))
+ / (duration * fade))
+ : 1));
+
+ draw_label (mi, &bp->walkers[0], 1.5, scale, s);
+
+ DONE:
+ free (s0);
+}
+#endif /* WORDBUBBLES */
+
+
+
+typedef struct {
+ int i;
+ GLdouble d;
+} depth_sorter;
+
+static int
+cmp_depth_sorter (const void *aa, const void *bb)
+{
+ const depth_sorter *a = (depth_sorter *) aa;
+ const depth_sorter *b = (depth_sorter *) bb;
+ return (a->d == b->d ? 0 : a->d < b->d ? -1 : 1);
+}
+
+
+ENTRYPOINT void
+draw_robot (ModeInfo *mi)
+{
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ GLfloat robot_size;
+ depth_sorter *sorted;
+ int i;
+
+ if (!bp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix ();
+
+# ifdef HAVE_MOBILE
+ glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
+# endif
+
+ gltrackball_rotate (bp->user_trackball);
+
+ glTranslatef (0, -20, 0); /* Move the horizon down the screen */
+
+ robot_size = size * 7;
+ glScalef (robot_size, robot_size, robot_size);
+ look_at_center (mi);
+
+ glPushMatrix();
+ glScalef (1/robot_size, 1/robot_size, 1/robot_size);
+ glCallList (bp->dlists[GROUND]);
+ glPopMatrix();
+
+# ifdef WORDBUBBLES
+ fill_words (mi);
+ bubble (mi);
+# endif
+
+#ifdef DEBUG
+ if (debug_p)
+ {
+ glTranslatef (0, -1.2, 0);
+ glScalef (3, 3, 3);
+ glRotatef (-43.5, 0, 0, 1);
+ glRotatef (-90, 0, 1, 0);
+
+ /* glTranslatef (bp->debug_z, bp->debug_y, 0); */
+
+ glPushMatrix();
+ if (!MI_IS_WIREFRAME(mi)) glDisable(GL_LIGHTING);
+ if (!MI_IS_WIREFRAME(mi) && do_texture) glDisable(GL_TEXTURE_2D);
+
+ glBegin(GL_LINES);
+ glVertex3f(-10, 0, 0); glVertex3f(10, 0, 0);
+ glVertex3f(0, -10, 0); glVertex3f(0, 10, 0);
+ glVertex3f(0, 0, -10); glVertex3f(0, 0, 10);
+ glEnd();
+
+ glTranslatef (-0.5, 0, -0.5);
+
+ glColor3f (1, 0, 0);
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
+ glVertex3f (0, 1, 1); glVertex3f (0, 1, 0);
+ glEnd();
+
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (1, 0, 0); glVertex3f (1, 0, 1);
+ glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
+ glEnd();
+
+# if 1
+ glColor3f (0.5, 0.5, 0.5);
+ glFrontFace (GL_CCW);
+ glBegin (GL_QUADS);
+ /* glVertex3f (0, 1, 0); glVertex3f (0, 1, 1); */
+ /* glVertex3f (1, 1, 1); glVertex3f (1, 1, 0); */
+ glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
+ glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
+ glEnd();
+
+ glFrontFace (GL_CW);
+ glBegin (GL_QUADS);
+ glVertex3f (0, 1, 0); glVertex3f (0, 1, 1);
+ glVertex3f (1, 1, 1); glVertex3f (1, 1, 0);
+ glVertex3f (0, 0, 0); glVertex3f (0, 0, 1);
+ glVertex3f (1, 0, 1); glVertex3f (1, 0, 0);
+ glEnd();
+# endif
+
+ glColor3f (1, 0, 0);
+ glBegin (GL_LINES);
+ glVertex3f (0, 0, 0); glVertex3f (1, 0, 0);
+ glVertex3f (0, 0, 1); glVertex3f (1, 0, 1);
+ glVertex3f (0, 1, 0); glVertex3f (1, 1, 0);
+ glVertex3f (0, 1, 1); glVertex3f (1, 1, 1);
+
+ glVertex3f (0, 0, 0); glVertex3f (1, 0, 1);
+ glVertex3f (0, 0, 1); glVertex3f (1, 0, 0);
+ glVertex3f (0, 1, 0); glVertex3f (1, 1, 1);
+ glVertex3f (0, 1, 1); glVertex3f (1, 1, 0);
+ glEnd();
+
+ if (!MI_IS_WIREFRAME(mi)) glEnable(GL_LIGHTING);
+ if (!MI_IS_WIREFRAME(mi) && do_texture) glEnable(GL_TEXTURE_2D);
+
+ glPopMatrix();
+ }
+
+# endif /* DEBUG */
+
+ /* For transparency to work right, we have to draw the robots from
+ back to front, from the perspective of the observer. So project
+ the origin of the robot to screen coordinates, and sort that by Z.
+ */
+ sorted = (depth_sorter *) calloc (bp->nwalkers, sizeof (*sorted));
+ {
+ GLdouble m[16], p[16];
+ GLint v[4];
+ glGetDoublev (GL_MODELVIEW_MATRIX, m);
+ glGetDoublev (GL_PROJECTION_MATRIX, p);
+ glGetIntegerv (GL_VIEWPORT, v);
+
+ for (i = 0; i < bp->nwalkers; i++)
+ {
+ GLdouble x, y, z;
+ walker *f = &bp->walkers[i];
+ gluProject (f->y, f->z, f->x, m, p, v, &x, &y, &z);
+ sorted[i].i = i;
+ sorted[i].d = -z;
+ }
+ qsort (sorted, i, sizeof(*sorted), cmp_depth_sorter);
+ }
+
+
+ mi->polygon_count = 0;
+ for (i = 0; i < bp->nwalkers; i++)
+ {
+ int ii = sorted[i].i;
+ walker *f = &bp->walkers[ii];
+ int ticks = 22 * speed * f->speed;
+ int max = 180;
+ char tag[1024];
+ *tag = 0;
+
+ if (ticks < 1) ticks = 1;
+ if (ticks > max) ticks = max;
+
+# ifdef DEBUG
+ if (debug_p)
+ sprintf (tag, "%.4f, %.4f, %.4f",
+ bp->debug_x, bp->debug_y, bp->debug_z);
+ else
+ {
+# if 1
+ /* sprintf (tag + strlen(tag), " %d\n", ii); */
+ sprintf (tag + strlen(tag), " %d\n", bp->nwalkers - i - 1);
+ /* sprintf (tag + strlen(tag), "%.03f\n", sorted[i].d); */
+# endif
+ }
+
+# endif /* DEBUG */
+
+ mi->polygon_count += draw_walker (mi, f, tag);
+
+ for (ii = 0; ii < ticks; ii++)
+ tick_walker (mi, f);
+ }
+ free (sorted);
+
+ glPopMatrix ();
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+ENTRYPOINT void
+free_robot (ModeInfo *mi)
+{
+# ifdef WORDBUBBLES
+ robot_configuration *bp = &bps[MI_SCREEN(mi)];
+ if (bp->tc)
+ textclient_close (bp->tc);
+# endif
+}
+
+XSCREENSAVER_MODULE_2 ("WindupRobot", winduprobot, robot)
+
+#endif /* USE_GL */