X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fgeodesicgears.c;fp=hacks%2Fglx%2Fgeodesicgears.c;h=83127c42178f072511e645d9a4aec2148f01ad02;hp=0000000000000000000000000000000000000000;hb=d5186197bc394e10a4402f7f6d23fbb14103bc50;hpb=6afd6db0ae9396cd7ff897ade597cd5483f49b0e diff --git a/hacks/glx/geodesicgears.c b/hacks/glx/geodesicgears.c new file mode 100644 index 00000000..83127c42 --- /dev/null +++ b/hacks/glx/geodesicgears.c @@ -0,0 +1,1783 @@ +/* geodesicgears, Copyright (c) 2014 Jamie Zawinski + * + * 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. + * + * Inspired by http://bugman123.com/Gears/ + * and by http://kennethsnelson.net/PortraitOfAnAtom.pdf + */ + +#define DEFAULTS "*delay: 30000 \n" \ + "*count: 4 \n" \ + "*wireframe: False \n" \ + "*showFPS: False \n" \ + "*texFontCacheSize: 100 \n" \ + "*font: -*-helvetica-medium-r-normal-*-*-160-*-*-*-*-*-*\n" \ + +# define refresh_geodesic 0 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#include "xlockmore.h" +#include "involute.h" +#include "colors.h" +#include "normals.h" +#include "rotator.h" +#include "gltrackball.h" +#include +#include "texfont.h" + + +#ifdef USE_GL /* whole file */ + +#include "gllist.h" + +#define DEF_SPIN "True" +#define DEF_WANDER "True" +#define DEF_SPEED "1.0" +#define DEF_LABELS "False" +#define DEF_NUMBERS "False" +#define DEF_TIMEOUT "20" + +typedef struct { double a, o; } LL; /* latitude + longitude */ + +/* 10:6 is a mismesh. */ + +static const struct { + enum { PRISM, OCTO, DECA, G14, G18, G32, G92, G182 } type; + const GLfloat args[5]; +} gear_templates[] = { + { PRISM }, + { OCTO }, + { DECA }, + { G14 }, + { G18 }, + { G32, { 15, 6, 0.4535 }}, /* teeth1, teeth2, radius1 */ + { G32, { 15, 12, 0.3560 }}, + { G32, { 20, 6, 0.4850 }}, + { G32, { 20, 12, 0.3995 }}, /* double of 10:6 */ + { G32, { 20, 18, 0.3375 }}, + { G32, { 25, 6, 0.5065 }}, + { G32, { 25, 12, 0.4300 }}, + { G32, { 25, 18, 0.3725 }}, + { G32, { 25, 24, 0.3270 }}, + { G32, { 30, 12, 0.4535 }}, /* double of 15:6 */ + { G32, { 30, 18, 0.3995 }}, + { G32, { 30, 24, 0.3560 }}, /* double of 15:12 */ + { G32, { 30, 30, 0.3205 }}, + { G32, { 35, 12, 0.4710 }}, + { G32, { 35, 18, 0.4208 }}, + { G32, { 35, 24, 0.3800 }}, + { G32, { 35, 30, 0.3450 }}, + { G32, { 35, 36, 0.3160 }}, + { G32, { 40, 12, 0.4850 }}, /* double of 20:6 */ + { G32, { 40, 24, 0.3995 }}, /* double of 10:6, 20:12 */ +/*{ G32, { 40, 36, 0.3375 }},*/ /* double of 20:18 */ + { G32, { 50, 12, 0.5065 }}, /* double of 25:6 */ + { G32, { 50, 24, 0.4300 }}, /* double of 25:12 */ + + /* These all have phase errors and don't always mesh properly. + Maybe we should just omit them? */ + + { G92, { 35, 36, 16, 0.2660, 0.366 }}, /* teeth1, 2, 3, r1, pitch3 */ + { G92, { 25, 36, 11, 0.2270, 0.315 }}, +/*{ G92, { 15, 15, 8, 0.2650, 0.356 }},*/ +/*{ G92, { 20, 21, 8, 0.2760, 0.355 }},*/ + { G92, { 25, 27, 16, 0.2320, 0.359 }}, + { G92, { 20, 36, 11, 0.1875, 0.283 }}, + { G92, { 30, 30, 16, 0.2585, 0.374 }}, /* double of 15:15:8 */ + { G92, { 20, 33, 11, 0.1970, 0.293 }}, +/*{ G92, { 10, 12, 8, 0.2030, 0.345 }},*/ + { G92, { 30, 33, 16, 0.2455, 0.354 }}, +/*{ G92, { 25, 24, 8, 0.3050, 0.375 }},*/ + { G92, { 20, 24, 16, 0.2030, 0.346 }}, +}; + + +typedef struct sphere_gear sphere_gear; +struct sphere_gear { + int id; /* name, for debugging */ + XYZ axis; /* the vector on which this gear's axis lies */ + int direction; /* rotation, +1 or -1 */ + GLfloat offset; /* rotational degrees from parent gear */ + sphere_gear *parent; /* gear driving this one, or 0 for root */ + sphere_gear **children; /* gears driven by this one (no loops) */ + sphere_gear **neighbors; /* gears touching this one (circular!) */ + int nchildren, children_size; + int nneighbors, neighbors_size; + const gear *g; /* shape of this gear (shared) */ +}; + + +typedef struct { + GLXContext *glx_context; + rotator *rot; + trackball_state *trackball; + Bool button_down_p; + int ncolors; + XColor *colors; + GLfloat color1[4], color2[4]; + texture_font_data *font; + + int nshapes, shapes_size; /* how many 'gear' objects there are */ + int ngears, gears_size; /* how many 'sphere_gear' objects there are */ + gear *shapes; + sphere_gear *gears; + + int which; + int mode; /* 0 = normal, 1 = out, 2 = in */ + int mode_tick; + int next; /* 0 = random, -1 = back, 1 = forward */ + time_t draw_time; + int draw_tick; + char *desc; + + GLfloat th; /* rotation of the root sphere_gear in degrees. */ + +} geodesic_configuration; + +static geodesic_configuration *bps = NULL; + +static int timeout; +static Bool do_spin; +static GLfloat speed; +static Bool do_wander; +static Bool do_labels; +static Bool do_numbers; + +static XrmOptionDescRec opts[] = { + { "-spin", ".spin", XrmoptionNoArg, "True" }, + { "+spin", ".spin", XrmoptionNoArg, "False" }, + { "-speed", ".speed", XrmoptionSepArg, 0 }, + { "-wander", ".wander", XrmoptionNoArg, "True" }, + { "+wander", ".wander", XrmoptionNoArg, "False" }, + { "-labels", ".labels", XrmoptionNoArg, "True" }, + { "+labels", ".labels", XrmoptionNoArg, "False" }, + { "-numbers", ".numbers",XrmoptionNoArg, "True" }, + { "+numbers", ".numbers",XrmoptionNoArg, "False" }, + { "-timeout", ".timeout",XrmoptionSepArg, 0 }, +}; + +static argtype vars[] = { + {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool}, + {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, + {&speed, "speed", "Speed", DEF_SPEED, t_Float}, + {&do_labels, "labels", "Labels", DEF_LABELS, t_Bool}, + {&do_numbers,"numbers","Numbers",DEF_NUMBERS,t_Bool}, + {&timeout, "timeout","Seconds",DEF_TIMEOUT,t_Int}, +}; + +ENTRYPOINT ModeSpecOpt geodesic_opts = { + countof(opts), opts, countof(vars), vars, NULL}; + + +#undef BELLRAND +#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3) + + + +static XYZ +cross_product (XYZ a, XYZ b) +{ + XYZ c; + c.x = (a.y * b.z) - (a.z * b.y); + c.y = (a.z * b.x) - (a.x * b.z); + c.z = (a.x * b.y) - (a.y * b.x); + return c; +} + + +static GLfloat +dot_product (XYZ a, XYZ b) +{ + return (a.x * b.x) + (a.y * b.y) + (a.z * b.z); +} + + +static XYZ +normalize (XYZ v) +{ + GLfloat d = sqrt ((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); + if (d == 0) + v.x = v.y = v.z = 0; + else + { + v.x /= d; + v.y /= d; + v.z /= d; + } + return v; +} + + +static XYZ +polar_to_cartesian (LL v) +{ + XYZ p; + p.x = cos (v.a) * cos (v.o); + p.y = cos (v.a) * sin (v.o); + p.z = sin (v.a); + return p; +} + + + + +static gear * +add_gear_shape (ModeInfo *mi, GLfloat radius, int teeth) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + int wire = MI_IS_WIREFRAME(mi); + gear *g; + int i; + + if (bp->nshapes >= bp->shapes_size - 1) + { + bp->shapes_size = bp->shapes_size * 1.2 + 4; + bp->shapes = (gear *) + realloc (bp->shapes, bp->shapes_size * sizeof(*bp->shapes)); + } + g = &bp->shapes[bp->nshapes++]; + + memset (g, 0, sizeof(*g)); + + g->r = radius; + g->nteeth = teeth; + g->ratio = 1; + + g->tooth_h = g->r / (teeth * 0.4); + + if (g->tooth_h > 0.06) /* stubbier teeth when small tooth count. */ + g->tooth_h *= 0.6; + + g->thickness = 0.05 + BELLRAND(0.15); + g->thickness2 = g->thickness / 4; + g->thickness3 = g->thickness; + g->size = wire ? INVOLUTE_SMALL : INVOLUTE_LARGE; + + /* Move the disc's origin inward to make the edge of the disc be tangent + to the unit sphere. */ + g->z = 1 - sqrt (1 - (g->r * g->r)); + + /* #### This isn't quite right */ + g->tooth_slope = 1 + ((g->z * 2) / g->r); + + + /* Decide on shape of gear interior: + - just a ring with teeth; + - that, plus a thinner in-set "plate" in the middle; + - that, plus a thin raised "lip" on the inner plate; + - or, a wide lip (really, a thicker third inner plate.) + */ + if (wire) + ; + else if ((random() % 10) == 0) + { + /* inner_r can go all the way in; there's no inset disc. */ + g->inner_r = (g->r * 0.3) + frand((g->r - g->tooth_h/2) * 0.6); + g->inner_r2 = 0; + g->inner_r3 = 0; + } + else + { + /* inner_r doesn't go in very far; inner_r2 is an inset disc. */ + g->inner_r = (g->r * 0.5) + frand((g->r - g->tooth_h) * 0.4); + g->inner_r2 = (g->r * 0.1) + frand(g->inner_r * 0.5); + g->inner_r3 = 0; + + if (g->inner_r2 > (g->r * 0.2)) + { + int nn = (random() % 10); + if (nn <= 2) + g->inner_r3 = (g->r * 0.1) + frand(g->inner_r2 * 0.2); + else if (nn <= 7 && g->inner_r2 >= 0.1) + g->inner_r3 = g->inner_r2 - 0.01; + } + } + + /* If we have three discs, sometimes make the middle disc be spokes. + */ + if (g->inner_r3 && ((random() % 5) == 0)) + { + g->spokes = 2 + BELLRAND (5); + g->spoke_thickness = 1 + frand(7.0); + if (g->spokes == 2 && g->spoke_thickness < 2) + g->spoke_thickness += 1; + } + + /* Sometimes add little nubbly bits, if there is room. + */ + if (!wire && g->nteeth > 5) + { + double size = 0; + involute_biggest_ring (g, 0, &size, 0); + if (size > g->r * 0.2 && (random() % 5) == 0) + { + g->nubs = 1 + (random() % 16); + if (g->nubs > 8) g->nubs = 1; + } + } + + /* Decide how complex the polygon model should be. + */ + { + double pix = g->tooth_h * MI_HEIGHT(mi); /* approx. tooth size in pixels */ + if (pix <= 4) g->size = INVOLUTE_SMALL; + else if (pix <= 8) g->size = INVOLUTE_MEDIUM; + else if (pix <= 30) g->size = INVOLUTE_LARGE; + else g->size = INVOLUTE_HUGE; + } + + if (g->inner_r3 > g->inner_r2) abort(); + if (g->inner_r2 > g->inner_r) abort(); + if (g->inner_r > g->r) abort(); + + i = random() % bp->ncolors; + g->color[0] = bp->colors[i].red / 65536.0; + g->color[1] = bp->colors[i].green / 65536.0; + g->color[2] = bp->colors[i].blue / 65536.0; + g->color[3] = 1; + + i = (i + bp->ncolors / 2) % bp->ncolors; + g->color2[0] = bp->colors[i].red / 65536.0; + g->color2[1] = bp->colors[i].green / 65536.0; + g->color2[2] = bp->colors[i].blue / 65536.0; + g->color2[3] = 1; + + g->dlist = glGenLists (1); + glNewList (g->dlist, GL_COMPILE); + +#if 1 + { + gear G, *g2 = &G; + *g2 = *g; + + /* Move the gear inward so that its outer edge is on the disc, instead + of its midpoint. */ + g2->z += g2->thickness/2; + + /* 'radius' is at the surface but 'g->r' is at the center, so we need + to reverse the slope computation that involute.c does. */ + g2->r /= (1 + (g2->thickness * g2->tooth_slope / 2)); + + glPushMatrix(); + glTranslatef(g2->x, g2->y, -g2->z); + + /* Line up the center of the point of tooth 0 with "up". */ + glRotatef (90, 0, 0, 1); + glRotatef (180, 0, 1, 0); + glRotatef (-360.0 / g2->nteeth / 4, 0, 0, 1); + + g->polygons = draw_involute_gear (g2, wire); + glPopMatrix(); + } +# else /* draw discs */ + { + glPushMatrix(); + glTranslatef(g->x, g->y, -g->z); + glLineWidth (2); + glFrontFace (GL_CCW); + glNormal3f(0, 0, 1); + glColor3f(0, 0, 0); + glDisable (GL_LIGHTING); + + glBegin(GL_LINES); + glVertex3f (0, 0, 0); + glVertex3f (0, radius, 0); + glEnd(); + + glColor3f(0.5, 0.5, 0.5); + glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN); + { + GLfloat th; + GLfloat step = M_PI * 2 / 128; + /* radius *= 1.005; */ + glVertex3f (0, 0, 0); + for (th = 0; th < M_PI * 2 + step; th += step) + { + GLfloat x = cos(th) * radius; + GLfloat y = sin(th) * radius; + glVertex3f (x, y, 0); + } + } + glEnd(); + if (!wire) glEnable(GL_LIGHTING); + glPopMatrix(); + } +# endif /* 0 */ + + glEndList (); + + return g; +} + + +static void +add_sphere_gear (ModeInfo *mi, gear *g, XYZ axis) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + sphere_gear *gg; + int i; + + axis = normalize (axis); + + /* If there's already a gear on this axis, don't duplicate it. */ + for (i = 0; i < bp->ngears; i++) + { + XYZ o = bp->gears[i].axis; + if (o.x == axis.x && o.y == axis.y && o.z == axis.z) + return; + } + + if (bp->ngears >= bp->gears_size - 1) + { + bp->gears_size = bp->gears_size * 1.2 + 10; + bp->gears = (sphere_gear *) + realloc (bp->gears, bp->gears_size * sizeof(*bp->gears)); + } + + gg = &bp->gears[bp->ngears]; + memset (gg, 0, sizeof(*gg)); + gg->id = bp->ngears; + gg->axis = axis; + gg->direction = 0; + gg->g = g; + bp->ngears++; +} + + +static void +free_sphere_gears (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + int i; + for (i = 0; i < bp->nshapes; i++) + { + if (bp->shapes[i].dlist) + glDeleteLists (bp->shapes[i].dlist, 1); + } + free (bp->shapes); + bp->nshapes = 0; + bp->shapes_size = 0; + bp->shapes = 0; + + for (i = 0; i < bp->ngears; i++) + { + sphere_gear *g = &bp->gears[i]; + if (g->children) + free (g->children); + if (g->neighbors) + free (g->neighbors); + } + free (bp->gears); + bp->ngears = 0; + bp->gears_size = 0; + bp->gears = 0; +} + + + +/* Is the gear a member of the list? + */ +static Bool +gear_list_member (sphere_gear *g, sphere_gear **list, int count) +{ + int i; + for (i = 0; i < count; i++) + if (list[i] == g) return True; + return False; +} + + +/* Add the gear to the list, resizing it as needed. + */ +static void +gear_list_push (sphere_gear *g, + sphere_gear ***listP, + int *countP, int *sizeP) +{ + if (*countP >= (*sizeP) - 1) + { + *sizeP = (*sizeP) * 1.2 + 4; + *listP = (sphere_gear **) realloc (*listP, (*sizeP) * sizeof(**listP)); + } + (*listP)[*countP] = g; + (*countP)++; +} + + +/* Mark child and parent as being mutual neighbors. + */ +static void +link_neighbors (sphere_gear *parent, sphere_gear *child) +{ + if (child == parent) abort(); + + /* Add child to parent's list of neighbors */ + if (! gear_list_member (child, parent->neighbors, parent->nneighbors)) + { + gear_list_push (child, + &parent->neighbors, + &parent->nneighbors, + &parent->neighbors_size); + /* fprintf(stderr, "neighbor %2d -> %2d (%d)\n", parent->id, child->id, + parent->nneighbors); */ + } + + /* Add parent to child's list of neighbors */ + if (! gear_list_member (parent, child->neighbors, child->nneighbors)) + { + gear_list_push (parent, + &child->neighbors, + &child->nneighbors, + &child->neighbors_size); + /* fprintf(stderr, "neighbor %2d <- %2d\n", parent->id, child->id); */ + } +} + +/* Mark child as having parent, and vice versa. + */ +static void +link_child (sphere_gear *parent, sphere_gear *child) +{ + if (child == parent) abort(); + if (child->parent) return; + + gear_list_push (child, + &parent->children, + &parent->nchildren, + &parent->children_size); + child->parent = parent; + /* fprintf(stderr, "child %2d -> %2d (%d)\n", parent->id, child->id, + parent->nchildren); */ +} + + + +static void link_children (sphere_gear *); + +static void +link_children (sphere_gear *parent) +{ + int i; +# if 1 /* depth first */ + for (i = 0; i < parent->nneighbors; i++) + { + sphere_gear *child = parent->neighbors[i]; + if (! child->parent) + { + link_child (parent, child); + link_children (child); + } + } +# else /* breadth first */ + for (i = 0; i < parent->nneighbors; i++) + { + sphere_gear *child = parent->neighbors[i]; + if (! child->parent) + link_child (parent, child); + } + for (i = 0; i < parent->nchildren; i++) + { + sphere_gear *child = parent->children[i]; + link_children (child); + } +# endif +} + + + +/* Whether the two gears touch. + */ +static Bool +gears_touch_p (ModeInfo *mi, sphere_gear *a, sphere_gear *b) +{ + /* We need to know if the two discs on the surface overlap. + + Find the angle between the axis of each disc, and a point on its edge: + the axis between the hypotenuse and adjacent of a right triangle between + the disc's radius and the origin. + + R + _____ + |_| / + | / + 1 | / + |t/ t = asin(R) + |/ + + Find the angle between the axes of the two discs. + + | + | / angle = acos (v1 dot v2) + 1 | / axis = v1 cross v2 + | / 1 + | / + |/ + + If the sum of the first two angles is less than the third angle, + they touch. + */ + XYZ p1 = a->axis; + XYZ p2 = b->axis; + double t1 = asin (a->g->r); + double t2 = asin (b->g->r); + double th = acos (dot_product (p1, p2)); + + return (t1 + t2 >= th); +} + + +/* Set the rotation direction for the gear and its kids. + */ +static void +orient_gears (ModeInfo *mi, sphere_gear *g) +{ + int i; + if (g->parent) + g->direction = -g->parent->direction; + for (i = 0; i < g->nchildren; i++) + orient_gears (mi, g->children[i]); +} + + +/* Returns the global model coordinates of the given tooth of a gear. + */ +static XYZ +tooth_coords (const sphere_gear *s, int tooth) +{ + const gear *g = s->g; + GLfloat off = s->offset * (M_PI / 180) * g->ratio * s->direction; + GLfloat th = (tooth * M_PI * 2 / g->nteeth) - off; + XYZ axis; + GLfloat angle; + XYZ from = { 0, 1, 0 }; + XYZ to = s->axis; + XYZ p0, p1, p2; + GLfloat x, y, z, C, S, m[4][4]; + + axis = cross_product (from, to); + angle = acos (dot_product (from, to)); + + p0 = normalize (axis); + x = p0.x; + y = p0.y; + z = p0.z; + C = cos(angle); + S = sin(angle); + + /* this is what glRotatef does */ + m[0][0] = x*x * (1 - C) + C; + m[1][0] = x*y * (1 - C) - z*S; + m[2][0] = x*z * (1 - C) + y*S; + m[3][0] = 0; + + m[0][1] = y*x * (1 - C) + z*S; + m[1][1] = y*y * (1 - C) + C; + m[2][1] = y*z * (1 - C) - x*S; + m[3][1] = 0; + + m[0][2] = x*z * (1 - C) - y*S; + m[1][2] = y*z * (1 - C) + x*S; + m[2][2] = z*z * (1 - C) + C; + m[3][2] = 0; + + m[0][3] = 0; + m[1][3] = 0; + m[2][3] = 0; + m[3][3] = 1; + + /* The point to transform */ + p1.x = g->r * sin (th); + p1.z = g->r * cos (th); + p1.y = 1 - g->z; + p1 = normalize (p1); + + /* transformation result */ + p2.x = p1.x * m[0][0] + p1.y * m[1][0] + p1.z * m[2][0] + m[3][0]; + p2.y = p1.x * m[0][1] + p1.y * m[1][1] + p1.z * m[2][1] + m[3][1]; + p2.z = p1.x * m[0][2] + p1.y * m[1][2] + p1.z * m[2][2] + m[3][2]; + + return p2; +} + + +/* Returns the number of the tooth of the first gear that is closest + to any tooth of its parent. Also the position of the parent tooth. + */ +static int +parent_tooth (const sphere_gear *s, XYZ *parent) +{ + const sphere_gear *s2 = s->parent; + int i, j; + GLfloat min_dist = 99999; + int min_tooth = 0; + XYZ min_parent = { 0, 0, 0 }; + + if (s2) + for (i = 0; i < s->g->nteeth; i++) + { + XYZ p1 = tooth_coords (s, i); + for (j = 0; j < s2->g->nteeth; j++) + { + XYZ p2 = tooth_coords (s2, j); + XYZ d; + GLfloat dist; + d.x = p1.x - p2.x; + d.y = p1.y - p2.y; + d.z = p1.z - p2.z; + + dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z); + if (dist < min_dist) + { + min_dist = dist; + min_parent = p2; + min_tooth = i; + } + } + } + *parent = min_parent; + return min_tooth; +} + + +/* Make all of the gear's children's teeth mesh properly. + */ +static void align_gear_teeth (sphere_gear *s); +static void +align_gear_teeth (sphere_gear *s) +{ + int i; + XYZ pc; + + if (s->parent) + { + /* Iterate this gear's offset until we find a value for it that + minimizes the distance between this gear's parent-pointing + tooth, and the corresponding tooth on the parent. + */ + int pt = parent_tooth (s, &pc); + GLfloat range = 360 / s->g->nteeth; + GLfloat steps = 64; + GLfloat min_dist = 999999; + GLfloat min_off = 0; + GLfloat off; + + for (off = -range/2; off < range/2; off += range/steps) + { + XYZ tc, d; + GLfloat dist; + s->offset = off; + tc = tooth_coords (s, pt); + d.x = pc.x - tc.x; + d.y = pc.y - tc.y; + d.z = pc.z - tc.z; + dist = sqrt (d.x*d.x + d.y*d.y + d.z*d.z); + if (dist < min_dist) + { + min_dist = dist; + min_off = off; + } + } + + s->offset = min_off; + } + + /* Now do the children. We have to do it in parent/child order because + the offset we just computed for the parent affects everyone downstream. + */ + for (i = 0; i < s->nchildren; i++) + align_gear_teeth (s->children[i]); +} + + + +static void +describe_gears (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + int gears_per_teeth[1000]; + int i; + int lines = 0; + memset (gears_per_teeth, 0, sizeof(gears_per_teeth)); + for (i = 0; i < bp->ngears; i++) + gears_per_teeth[bp->gears[i].g->nteeth]++; + if (bp->desc) free (bp->desc); + bp->desc = (char *) malloc (80 * bp->ngears); + *bp->desc = 0; + for (i = 0; i < countof(gears_per_teeth); i++) + if (gears_per_teeth[i]) + { + sprintf (bp->desc + strlen(bp->desc), + "%s%d gears with %d teeth", + (lines > 0 ? ",\n" : ""), + gears_per_teeth[i], i); + lines++; + } + if (lines > 1) + sprintf (bp->desc + strlen(bp->desc), ",\n%d gears total", bp->ngears); + strcat (bp->desc, "."); +} + + +/* Takes the gears and makes an arbitrary DAG of them in order to compute + direction and gear ratios. + */ +static void +sort_gears (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + sphere_gear *root = 0; + int i, j; + + /* For each gear, compare it against every other gear. + If they touch, mark them as being each others' neighbors. + */ + for (i = 0; i < bp->ngears; i++) + { + sphere_gear *a = &bp->gears[i]; + for (j = 0; j < bp->ngears; j++) + { + sphere_gear *b = &bp->gears[j]; + if (a == b) continue; + if (gears_touch_p (mi, a, b)) + link_neighbors (a, b); + } + } + + bp->gears[0].parent = &bp->gears[0]; /* don't give this one a parent */ + link_children (&bp->gears[0]); + bp->gears[0].parent = 0; + + +# if 0 + for (i = 0; i < bp->ngears; i++) + { + fprintf (stderr, "%2d: p = %2d; k(%d, %d) = ", + i, + bp->gears[i].parent ? bp->gears[i].parent->id : -1, + bp->gears[i].nneighbors, + bp->gears[i].nchildren); + for (j = 0; j < bp->gears[i].nneighbors; j++) + fprintf (stderr, "%2d ", (int) bp->gears[i].neighbors[j]->id); + fprintf (stderr, "\t\t"); + if (j < 5) fprintf (stderr, "\t"); + for (j = 0; j < bp->gears[i].nchildren; j++) + fprintf (stderr, "%2d ", (int) bp->gears[i].children[j]->id); + fprintf (stderr,"\n"); + } + fprintf (stderr,"\n"); +# endif /* 0 */ + + + /* If there is more than one gear with no parent, we fucked up. */ + + root = 0; + for (i = 0; i < bp->ngears; i++) + { + sphere_gear *g = &bp->gears[i]; + if (!g->parent) + root = g; + } + + if (! root) abort(); + + root->direction = 1; + orient_gears (mi, root); + + /* If there are any gears with no direction, they aren't reachable. */ + for (i = 0; i < bp->ngears; i++) + { + sphere_gear *g = &bp->gears[i]; + if (g->direction == 0) + fprintf(stderr,"INTERNAL ERROR: unreachable: %d\n", g->id); + } + + align_gear_teeth (root); + describe_gears (mi); +} + + +/* Create 5 identical gears arranged on the faces of a uniform + triangular prism. + */ +static void +make_prism (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + gear *g; + XYZ a; + int i; + int teeth = 4 * (4 + (int) (BELLRAND(20))); + if (teeth % 4) abort(); /* must be a multiple of 4 */ + + g = add_gear_shape (mi, 0.7075, teeth); + + a.x = 0; a.y = 0; a.z = 1; + add_sphere_gear (mi, g, a); + a.z = -1; + add_sphere_gear (mi, g, a); + + a.z = 0; + for (i = 0; i < 3; i++) + { + GLfloat th = i * M_PI * 2 / 3; + a.x = cos (th); + a.y = sin (th); + add_sphere_gear (mi, g, a); + } + + if (bp->ngears != 5) abort(); +} + + +/* Create 8 identical gears arranged on the faces of an octohedron + (or alternately, arranged on the diagonals of a cube.) + */ +static void +make_octo (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + static const XYZ verts[] = {{ -1, -1, -1 }, + { -1, -1, 1 }, + { -1, 1, 1 }, + { -1, 1, -1 }, + { 1, -1, 1 }, + { 1, -1, -1 }, + { 1, 1, -1 }, + { 1, 1, 1 }}; + gear *g; + int i; + int teeth = 4 * (4 + (int) (BELLRAND(20))); + if (teeth % 4) abort(); /* must be a multiple of 4 */ + + g = add_gear_shape (mi, 0.578, teeth); + for (i = 0; i < countof(verts); i++) + add_sphere_gear (mi, g, verts[i]); + + if (bp->ngears != 8) abort(); +} + + +/* Create 10 identical gears arranged on the faces of ... something. + I'm not sure what polyhedron is the basis of this. + */ +static void +make_deca (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + gear *g; + XYZ a; + int i, j; + int teeth = 4 * (4 + (int) (BELLRAND(15))); + if (teeth % 4) abort(); /* must be a multiple of 4 */ + + g = add_gear_shape (mi, 0.5415, teeth); + + a.x = 0; a.y = 0; a.z = 1; + add_sphere_gear (mi, g, a); + a.z = -1; + add_sphere_gear (mi, g, a); + + for (j = -1; j <= 1; j += 2) + { + GLfloat off = (j < 0 ? 0 : M_PI / 4); + LL v; + v.a = j * M_PI * 0.136; /* #### Empirical. What is this? */ + for (i = 0; i < 4; i++) + { + v.o = i * M_PI / 2 + off; + a = polar_to_cartesian (v); + add_sphere_gear (mi, g, a); + } + } + if (bp->ngears != 10) abort(); +} + + +/* Create 14 identical gears arranged on the faces of ... something. + I'm not sure what polyhedron is the basis of this. + */ +static void +make_14 (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + gear *g; + XYZ a; + int i; + GLfloat r = 0.4610; + int teeth = 6 * (2 + (int) (BELLRAND(4))); + if (teeth % 6) abort(); /* must be a multiple of 6. I think? */ + /* mismeshes: 24 30 34 36 42 48 54 60 */ + + /* North, south */ + g = add_gear_shape (mi, r, teeth); + a.x = 0; a.y = 0; a.z = 1; + add_sphere_gear (mi, g, a); + a.z = -1; + add_sphere_gear (mi, g, a); + + /* Equator */ + a.z = 0; + for (i = 0; i < 4; i++) + { + GLfloat th = i * M_PI * 2 / 4 + (M_PI / 4); + a.x = cos(th); + a.y = sin(th); + add_sphere_gear (mi, g, a); + } + + /* The other 8 */ + g = add_gear_shape (mi, r, teeth); + + for (i = 0; i < 4; i++) + { + LL v; + v.a = M_PI * 0.197; /* #### Empirical. Also, wrong. What is this? */ + v.o = i * M_PI * 2 / 4; + a = polar_to_cartesian (v); + add_sphere_gear (mi, g, a); + v.a = -v.a; + a = polar_to_cartesian (v); + add_sphere_gear (mi, g, a); + } + + if (bp->ngears != 14) abort(); +} + + +/* Create 18 identical gears arranged on the faces of ... something. + I'm not sure what polyhedron is the basis of this. + */ +static void +make_18 (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + gear *g, *g2; + XYZ a; + int i; + GLfloat r = 0.3830; + int sizes[] = { 8, 12, 16, 20 }; /* 10, 14, 18, 26 and 34 don't work */ + int teeth = sizes[random() % countof(sizes)] * (1 + (random() % 4)); + + /* North, south */ + g = add_gear_shape (mi, r, teeth); + a.x = 0; a.y = 0; a.z = 1; + add_sphere_gear (mi, g, a); + a.z = -1; + add_sphere_gear (mi, g, a); + + /* Equator */ + g2 = add_gear_shape (mi, r, teeth); + a.z = 0; + for (i = 0; i < 8; i++) + { + GLfloat th = i * M_PI * 2 / 8 + (M_PI / 4); + a.x = cos(th); + a.y = sin(th); + add_sphere_gear (mi, (i & 1 ? g : g2), a); + } + + /* The other 16 */ + g = add_gear_shape (mi, r, teeth); + + for (i = 0; i < 4; i++) + { + LL v; + v.a = M_PI * 0.25; + v.o = i * M_PI * 2 / 4; + a = polar_to_cartesian (v); + add_sphere_gear (mi, g, a); + v.a = -v.a; + a = polar_to_cartesian (v); + add_sphere_gear (mi, g, a); + } + + if (bp->ngears != 18) abort(); +} + + +/* Create 32 gears arranged along a truncated icosahedron: + One gear on each of the 20 faces, and one on each of the 12 vertices. + */ +static void +make_32 (ModeInfo *mi, const GLfloat *args) +{ + /* http://bugman123.com/Gears/32GearSpheres/ */ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */ + GLfloat s = M_PI / 5; /* lon division: 72 deg */ + int i; + + int teeth1 = args[0]; + int teeth2 = args[1]; + GLfloat r1 = args[2]; + GLfloat ratio = teeth2 / (GLfloat) teeth1; + GLfloat r2 = r1 * ratio; + + gear *gear1, *gear2; + + if (teeth1 % 5) abort(); + if (teeth2 % 6) abort(); + + gear1 = add_gear_shape (mi, r1, teeth1); + gear2 = add_gear_shape (mi, r2, teeth2); + gear2->ratio = 1 / ratio; + + { + XYZ a = { 0, 0, 1 }; + XYZ b = { 0, 0, -1 }; + add_sphere_gear (mi, gear1, a); + add_sphere_gear (mi, gear1, b); + } + + for (i = 0; i < 10; i++) + { + GLfloat th1 = s * i; + GLfloat th2 = s * (i+1); + GLfloat th3 = s * (i+2); + LL v1, v2, v3, vc; + XYZ p1, p2, p3, pc, pc2; + v1.a = th0; v1.o = th1; + v2.a = th0; v2.o = th3; + v3.a = -th0; v3.o = th2; + vc.a = M_PI/2; vc.o = th2; + + if (! (i & 1)) /* southern hemisphere */ + { + v1.a = -v1.a; + v2.a = -v2.a; + v3.a = -v3.a; + vc.a = -vc.a; + } + + p1 = polar_to_cartesian (v1); + p2 = polar_to_cartesian (v2); + p3 = polar_to_cartesian (v3); + pc = polar_to_cartesian (vc); + + /* Two faces: 123 and 12c. */ + + add_sphere_gear (mi, gear1, p1); /* left shared point of 2 triangles */ + + pc2.x = (p1.x + p2.x + p3.x) / 3; /* center of bottom triangle */ + pc2.y = (p1.y + p2.y + p3.y) / 3; + pc2.z = (p1.z + p2.z + p3.z) / 3; + add_sphere_gear (mi, gear2, pc2); + + pc2.x = (p1.x + p2.x + pc.x) / 3; /* center of top triangle */ + pc2.y = (p1.y + p2.y + pc.y) / 3; + pc2.z = (p1.z + p2.z + pc.z) / 3; + add_sphere_gear (mi, gear2, pc2); + } + + if (bp->ngears != 32) abort(); +} + + +/* Create 92 gears arranged along a geodesic sphere: 20 + 12 + 60. + (frequency 3v, class-I geodesic tessellation of an icosahedron) + */ +static void +make_92 (ModeInfo *mi, const GLfloat *args) +{ + /* http://bugman123.com/Gears/92GearSpheres/ */ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */ + GLfloat s = M_PI / 5; /* lon division: 72 deg */ + int i; + + int tscale = 2; /* These don't mesh properly, so let's increase the + number of teeth so that it's not so obvious. */ + + int teeth1 = args[0] * tscale; + int teeth2 = args[1] * tscale; + int teeth3 = args[2] * tscale; + GLfloat r1 = args[3]; + GLfloat ratio2 = teeth2 / (GLfloat) teeth1; + GLfloat ratio3 = teeth3 / (GLfloat) teeth2; + GLfloat r2 = r1 * ratio2; + GLfloat r3 = r2 * ratio3; + + GLfloat r4 = args[4]; /* #### Empirical. Not sure what its basis is. */ + GLfloat r5 = 1 - r4; + + gear *gear1, *gear2, *gear3; + + gear1 = add_gear_shape (mi, r1, teeth1); + gear2 = add_gear_shape (mi, r2, teeth2); + gear3 = add_gear_shape (mi, r3, teeth3); + gear2->ratio = 1 / ratio2; + gear3->ratio = 1 / ratio3; + + { + XYZ a = { 0, 0, 1 }; + XYZ b = { 0, 0, -1 }; + add_sphere_gear (mi, gear1, a); + add_sphere_gear (mi, gear1, b); + } + + for (i = 0; i < 10; i++) + { + GLfloat th1 = s * i; + GLfloat th2 = s * (i+1); + GLfloat th3 = s * (i+2); + LL v1, v2, v3, vc; + XYZ p1, p2, p3, pc, pc2; + v1.a = th0; v1.o = th1; + v2.a = th0; v2.o = th3; + v3.a = -th0; v3.o = th2; + vc.a = M_PI/2; vc.o = th2; + + if (! (i & 1)) /* southern hemisphere */ + { + v1.a = -v1.a; + v2.a = -v2.a; + v3.a = -v3.a; + vc.a = -vc.a; + } + + p1 = polar_to_cartesian (v1); + p2 = polar_to_cartesian (v2); + p3 = polar_to_cartesian (v3); + pc = polar_to_cartesian (vc); + + /* Two faces: 123 and 12c. */ + + add_sphere_gear (mi, gear1, p1); /* left shared point of 2 triangles */ + + pc2.x = (p1.x + p2.x + p3.x) / 3; /* center of bottom triangle */ + pc2.y = (p1.y + p2.y + p3.y) / 3; + pc2.z = (p1.z + p2.z + p3.z) / 3; + add_sphere_gear (mi, gear2, pc2); + + pc2.x = (p1.x + p2.x + pc.x) / 3; /* center of top triangle */ + pc2.y = (p1.y + p2.y + pc.y) / 3; + pc2.z = (p1.z + p2.z + pc.z) / 3; + add_sphere_gear (mi, gear2, pc2); + + /* left edge of bottom triangle, 1/3 in */ + pc2.x = p1.x + (p3.x - p1.x) * r4; + pc2.y = p1.y + (p3.y - p1.y) * r4; + pc2.z = p1.z + (p3.z - p1.z) * r4; + add_sphere_gear (mi, gear3, pc2); + + /* left edge of bottom triangle, 2/3 in */ + pc2.x = p1.x + (p3.x - p1.x) * r5; + pc2.y = p1.y + (p3.y - p1.y) * r5; + pc2.z = p1.z + (p3.z - p1.z) * r5; + add_sphere_gear (mi, gear3, pc2); + + /* left edge of top triangle, 1/3 in */ + pc2.x = p1.x + (pc.x - p1.x) * r4; + pc2.y = p1.y + (pc.y - p1.y) * r4; + pc2.z = p1.z + (pc.z - p1.z) * r4; + add_sphere_gear (mi, gear3, pc2); + + /* left edge of top triangle, 2/3 in */ + pc2.x = p1.x + (pc.x - p1.x) * r5; + pc2.y = p1.y + (pc.y - p1.y) * r5; + pc2.z = p1.z + (pc.z - p1.z) * r5; + add_sphere_gear (mi, gear3, pc2); + + /* center of shared edge, 1/3 in */ + pc2.x = p1.x + (p2.x - p1.x) * r4; + pc2.y = p1.y + (p2.y - p1.y) * r4; + pc2.z = p1.z + (p2.z - p1.z) * r4; + add_sphere_gear (mi, gear3, pc2); + + /* center of shared edge, 2/3 in */ + pc2.x = p1.x + (p2.x - p1.x) * r5; + pc2.y = p1.y + (p2.y - p1.y) * r5; + pc2.z = p1.z + (p2.z - p1.z) * r5; + add_sphere_gear (mi, gear3, pc2); + } + + if (bp->ngears != 92) abort(); +} + +static void +make_182 (ModeInfo *mi, const GLfloat *args) +{ + /* #### TODO: http://bugman123.com/Gears/182GearSpheres/ */ + abort(); +} + + +/* Window management, etc + */ +ENTRYPOINT void +reshape_geodesic (ModeInfo *mi, int width, int height) +{ + GLfloat h = (GLfloat) height / (GLfloat) width; + + glViewport (0, 0, (GLint) width, (GLint) height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective (30.0, 1/h, 1.0, 100.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( 0.0, 0.0, 30.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); + + glClear(GL_COLOR_BUFFER_BIT); +} + + +static void +pick_shape (ModeInfo *mi, time_t last) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + int count = countof (gear_templates); + + if (bp->colors) + free (bp->colors); + + bp->ncolors = 1024; + bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor)); + make_smooth_colormap (0, 0, 0, + bp->colors, &bp->ncolors, + False, 0, False); + + free_sphere_gears (mi); + + if (last == 0) + { + bp->which = random() % count; + } + else if (bp->next < 0) + { + bp->which--; + if (bp->which < 0) bp->which = count-1; + bp->next = 0; + } + else if (bp->next > 0) + { + bp->which++; + if (bp->which >= count) bp->which = 0; + bp->next = 0; + } + else + { + int n = bp->which; + while (n == bp->which) + n = random() % count; + bp->which = n; + } + + switch (gear_templates[bp->which].type) { + case PRISM: make_prism (mi); break; + case OCTO: make_octo (mi); break; + case DECA: make_deca (mi); break; + case G14: make_14 (mi); break; + case G18: make_18 (mi); break; + case G32: make_32 (mi, gear_templates[bp->which].args); break; + case G92: make_92 (mi, gear_templates[bp->which].args); break; + case G182: make_182(mi, gear_templates[bp->which].args); break; + default: abort(); break; + } + + sort_gears (mi); +} + + + +ENTRYPOINT void +init_geodesic (ModeInfo *mi) +{ + geodesic_configuration *bp; + int wire = MI_IS_WIREFRAME(mi); + + if (!bps) { + bps = (geodesic_configuration *) + calloc (MI_NUM_SCREENS(mi), sizeof (geodesic_configuration)); + if (!bps) { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + } + + bp = &bps[MI_SCREEN(mi)]; + + bp->glx_context = init_GL(mi); + + reshape_geodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + + { + static GLfloat cspec[4] = {1.0, 1.0, 1.0, 1.0}; + static const GLfloat shiny = 128.0; + + static GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0}; + static GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0}; + static GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; + static GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0}; + + glLightfv(GL_LIGHT0, GL_POSITION, pos); + glLightfv(GL_LIGHT0, GL_AMBIENT, amb); + glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); + glLightfv(GL_LIGHT0, GL_SPECULAR, spc); + + glMaterialfv (GL_FRONT, GL_SPECULAR, cspec); + glMateriali (GL_FRONT, GL_SHININESS, shiny); + } + + if (! wire) + { + glEnable (GL_DEPTH_TEST); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_LIGHTING); + glEnable (GL_LIGHT0); + } + + if (! bp->rot) + { + double spin_speed = 0.25 * speed; + double wander_speed = 0.01 * speed; + double spin_accel = 0.2; + + bp->rot = make_rotator (do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + spin_accel, + do_wander ? wander_speed : 0, + True); + bp->trackball = gltrackball_init (True); + } + + bp->font = load_texture_font (MI_DISPLAY(mi), "font"); + + pick_shape (mi, 0); +} + + +ENTRYPOINT Bool +geodesic_handle_event (ModeInfo *mi, XEvent *event) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + + if (gltrackball_event_handler (event, bp->trackball, + MI_WIDTH (mi), MI_HEIGHT (mi), + &bp->button_down_p)) + return True; + else + { + if (event->xany.type == KeyPress) + { + KeySym keysym; + char c = 0; + XLookupString (&event->xkey, &c, 1, &keysym, 0); + if (c == '<' || c == ',' || c == '-' || c == '_' || + keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior) + { + bp->next = -1; + goto SWITCH; + } + else if (c == '>' || c == '.' || c == '=' || c == '+' || + keysym == XK_Right || keysym == XK_Down || + keysym == XK_Next) + { + bp->next = 1; + goto SWITCH; + } + } + + if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event)) + { + SWITCH: + bp->mode = 1; + bp->mode_tick = 4; + return True; + } + } + + return False; +} + + +ENTRYPOINT void +draw_geodesic (ModeInfo *mi) +{ + time_t now = time ((time_t *) 0); + int wire = MI_IS_WIREFRAME(mi); + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + + if (!bp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context)); + + + if (bp->draw_time == 0) + { + pick_shape (mi, bp->draw_time); + bp->draw_time = now; + } + else if (bp->mode == 0) + { + if (bp->draw_tick++ > 10) + { + if (bp->draw_time == 0) bp->draw_time = now; + bp->draw_tick = 0; + + if (!bp->button_down_p && + bp->draw_time + timeout <= now) + { + /* randomize every -timeout seconds */ + bp->mode = 1; /* go out */ + bp->mode_tick = 10 / speed; + bp->draw_time = now; + } + } + } + else if (bp->mode == 1) /* out */ + { + if (--bp->mode_tick <= 0) + { + bp->mode_tick = 10 / speed; + bp->mode = 2; /* go in */ + pick_shape (mi, bp->draw_time); + bp->draw_time = now; + } + } + else if (bp->mode == 2) /* in */ + { + if (--bp->mode_tick <= 0) + bp->mode = 0; /* normal */ + } + else + abort(); + + + if (! wire) + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_NORMALIZE); + glEnable(GL_CULL_FACE); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix (); + + { + double x, y, z; + get_position (bp->rot, &x, &y, &z, !bp->button_down_p); + glTranslatef((x - 0.5) * 8, + (y - 0.5) * 8, + (z - 0.5) * 17); + + gltrackball_rotate (bp->trackball); + + get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p); + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); + } + + mi->polygon_count = 0; + + glScalef (6, 6, 6); + + if (bp->ngears < 14) + glScalef (0.8, 0.8, 0.8); /* make these a little easier to see */ + + if (bp->mode != 0) + { + GLfloat s = (bp->mode == 1 + ? bp->mode_tick / (10 / speed) + : ((10 / speed) - bp->mode_tick + 1) / (10 / speed)); + glScalef (s, s, s); + } + + + { + int i; + for (i = 0; i < bp->ngears; i++) + { + const sphere_gear *s = &bp->gears[i]; + const gear *g = s->g; + + XYZ axis; + XYZ from = { 0, 1, 0 }; + XYZ to = s->axis; + GLfloat angle; + GLfloat off = s->offset; + + /* If an even number of teeth, offset by 1/2 tooth width. */ + if (s->direction > 0 && !(g->nteeth & 1)) + off += 360 / g->nteeth / 2; + + axis = cross_product (from, to); + angle = acos (dot_product (from, to)); + + glPushMatrix(); + glTranslatef (to.x, to.y, to.z); + glRotatef (angle / M_PI * 180, axis.x, axis.y, axis.z); + glRotatef (-90, 1, 0, 0); + glRotatef(180, 0, 0, 1); + glRotatef ((bp->th - off) * g->ratio * s->direction, + 0, 0, 1); + + glCallList (g->dlist); + mi->polygon_count += g->polygons; + glPopMatrix(); + +#if 0 + { /* Draw tooth vectors */ + GLfloat r = 1 - g->z; + XYZ pc; + int pt = parent_tooth (s, &pc); + int t; + glDisable(GL_LIGHTING); + glLineWidth (8); + for (t = 0; t < g->nteeth; t++) + { + XYZ p = tooth_coords (s, t); + XYZ p2; + p2.x = (r * s->axis.x + p.x) / 2; + p2.y = (r * s->axis.y + p.y) / 2; + p2.z = (r * s->axis.z + p.z) / 2; + + if (t == pt) + glColor3f(1,0,1); + else + glColor3f(0,1,1); + glBegin(GL_LINES); + glVertex3f (p.x, p.y, p.z); + glVertex3f (p2.x, p2.y, p2.z); + glEnd(); + } + if (!wire) glEnable(GL_LIGHTING); + glLineWidth (1); + } +#endif + +#if 0 + { /* Draw the parent/child DAG */ + GLfloat s1 = 1.1; + GLfloat s2 = s->parent ? s1 : 1.5; + GLfloat s3 = 1.0; + XYZ p1 = s->axis; + XYZ p2 = s->parent ? s->parent->axis : p1; + glDisable(GL_LIGHTING); + glColor3f(0,0,1); + glBegin(GL_LINES); + glVertex3f (s1 * p1.x, s1 * p1.y, s1 * p1.z); + glVertex3f (s2 * p2.x, s2 * p2.y, s2 * p2.z); + glVertex3f (s1 * p1.x, s1 * p1.y, s1 * p1.z); + glVertex3f (s3 * p1.x, s3 * p1.y, s3 * p1.z); + glEnd(); + if (!wire) glEnable(GL_LIGHTING); + } +#endif + } + + /* We need to draw the fonts in a second pass in order to make + transparency (as a result of anti-aliasing) work properly. + */ + if (do_numbers && bp->mode == 0) + for (i = 0; i < bp->ngears; i++) + { + const sphere_gear *s = &bp->gears[i]; + const gear *g = s->g; + + XYZ axis; + XYZ from = { 0, 1, 0 }; + XYZ to = s->axis; + GLfloat angle; + GLfloat off = s->offset; + + int w, h, j; + char buf[100]; + + /* If an even number of teeth, offset by 1/2 tooth width. */ + if (s->direction > 0 /* && !(g->nteeth & 1) */) + off += 360 / g->nteeth / 2; + + axis = cross_product (from, to); + angle = acos (dot_product (from, to)); + + glPushMatrix(); + glTranslatef(to.x, to.y, to.z); + glRotatef (angle / M_PI * 180, axis.x, axis.y, axis.z); + glRotatef (-90, 1, 0, 0); + glRotatef(180, 0, 0, 1); + glRotatef ((bp->th - off) * g->ratio * s->direction, + 0, 0, 1); + + glDisable (GL_LIGHTING); + glColor3f(1, 1, 0); + glPushMatrix(); + glScalef(0.005, 0.005, 0.005); + sprintf (buf, "%d", i); + w = texture_string_width (bp->font, buf, &h); + glTranslatef (-w/2, -h*2/3, 0); + print_texture_string (bp->font, buf); + glPopMatrix(); + +# if 1 + for (j = 0; j < g->nteeth; j++) /* Number the teeth */ + { + GLfloat ss = 0.08 * g->r / g->nteeth; + GLfloat r = g->r * 0.88; + GLfloat th = M_PI - (j * M_PI * 2 / g->nteeth + M_PI/2); + + + glPushMatrix(); + glTranslatef (r * cos(th), r * sin(th), -g->z + 0.01); + glScalef(ss, ss, ss); + sprintf (buf, "%d", j + 1); + w = texture_string_width (bp->font, buf, &h); + glTranslatef (-w/2, -h*2/3, 0); + print_texture_string (bp->font, buf); + glPopMatrix(); + } +# endif + glPopMatrix(); + if (!wire) glEnable(GL_LIGHTING); + } + + bp->th += 0.7 * speed; /* Don't mod this to 360 - causes glitches. */ + } + + if (do_labels && bp->mode == 0) + { + glColor3f (1, 1, 0); + print_texture_label (mi->dpy, bp->font, + mi->xgwa.width, mi->xgwa.height, + 1, bp->desc); + } + + glPopMatrix (); + + if (mi->fps_p) do_fps (mi); + glFinish(); + + glXSwapBuffers(dpy, window); +} + +ENTRYPOINT void +release_geodesic (ModeInfo *mi) +{ + geodesic_configuration *bp = &bps[MI_SCREEN(mi)]; + free_texture_font (bp->font); + free (bp->colors); + free_sphere_gears (mi); + if (bp->desc) free (bp->desc); +} + + +XSCREENSAVER_MODULE_2 ("GeodesicGears", geodesicgears, geodesic) + +#endif /* USE_GL */