From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / dymaxionmap.c
1 /* dymaxionmap --- Buckminster Fuller's unwrapped icosahedral globe.
2  * Copyright (c) 2016 Jamie Zawinski.
3  *
4  * Permission to use, copy, modify, and distribute this software and its
5  * documentation for any purpose and without fee is hereby granted,
6  * provided that the above copyright notice appear in all copies and that
7  * both that copyright notice and this permission notice appear in
8  * supporting documentation.
9  *
10  * This file is provided AS IS with no warranties of any kind.  The author
11  * shall have no liability with respect to the infringement of copyrights,
12  * trade secrets or any patents by this file or any part thereof.  In no
13  * event will the author be liable for any lost revenue or profits or
14  * other special, indirect and consequential damages.
15  */
16
17 #define LABEL_FONT "-*-helvetica-bold-r-normal-*-*-240-*-*-*-*-*-*"
18
19 #ifdef STANDALONE
20 #define DEFAULTS    "*delay:            20000   \n" \
21                     "*showFPS:          False   \n" \
22                     "*wireframe:        False   \n" \
23                     "*labelFont:  " LABEL_FONT "\n"
24 # define refresh_planet 0
25 # define release_planet 0
26 # include "xlockmore.h"             /* from the xscreensaver distribution */
27 #else  /* !STANDALONE */
28 # include "xlock.h"                 /* from the xlockmore distribution */
29 #endif /* !STANDALONE */
30
31 #ifdef USE_GL /* whole file */
32
33 #include "sphere.h"
34 #include "normals.h"
35 #include "texfont.h"
36
37 #ifdef HAVE_XMU
38 # ifndef VMS
39 #  include <X11/Xmu/Drawing.h>
40 #else  /* VMS */
41 #  include <Xmu/Drawing.h>
42 # endif /* VMS */
43 #endif
44
45 #define DEF_ROTATE  "True"
46 #define DEF_ROLL    "True"
47 #define DEF_WANDER  "True"
48 #define DEF_TEXTURE "True"
49 #define DEF_STARS   "True"
50 #define DEF_SPEED   "1.0"
51 #define DEF_IMAGE   "BUILTIN"
52
53 #undef countof
54 #define countof(x) (sizeof((x))/sizeof((*x)))
55
56 #undef BELLRAND
57 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
58
59 static int do_roll;
60 static int do_wander;
61 static int do_texture;
62 static int do_stars;
63 static GLfloat speed;
64 static char *which_image;
65
66 static XrmOptionDescRec opts[] = {
67   {"-speed",   ".dymaxionmap.speed",   XrmoptionSepArg, 0 },
68   {"-roll",    ".dymaxionmap.roll",    XrmoptionNoArg, "true" },
69   {"+roll",    ".dymaxionmap.roll",    XrmoptionNoArg, "false" },
70   {"-wander",  ".dymaxionmap.wander",  XrmoptionNoArg, "true" },
71   {"+wander",  ".dymaxionmap.wander",  XrmoptionNoArg, "false" },
72   {"-texture", ".dymaxionmap.texture", XrmoptionNoArg, "true" },
73   {"+texture", ".dymaxionmap.texture", XrmoptionNoArg, "false" },
74   {"-stars",   ".dymaxionmap.stars",   XrmoptionNoArg, "true" },
75   {"+stars",   ".dymaxionmap.stars",   XrmoptionNoArg, "false" },
76   {"-image",   ".dymaxionmap.image",  XrmoptionSepArg, 0 },
77 };
78
79 static argtype vars[] = {
80   {&speed,       "speed",   "Speed",   DEF_SPEED,   t_Float},
81   {&do_roll,     "roll",    "Roll",    DEF_ROLL,    t_Bool},
82   {&do_wander,   "wander",  "Wander",  DEF_WANDER,  t_Bool},
83   {&do_texture,  "texture", "Texture", DEF_TEXTURE, t_Bool},
84   {&do_stars,    "stars",   "Stars",   DEF_STARS,   t_Bool},
85   {&which_image, "image",   "Image",   DEF_IMAGE,   t_String},
86 };
87
88 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
89
90 #ifdef USE_MODULES
91 ModStruct   planet_description =
92 {"planet", "init_planet", "draw_planet", NULL,
93  "draw_planet", "init_planet", NULL, &planet_opts,
94  1000, 1, 2, 1, 4, 1.0, "",
95  "Buckminster Fuller's unwrapped icosahedral globe", 0, NULL};
96 #endif
97
98 # ifdef __GNUC__
99   __extension__  /* don't warn about "string length is greater than the length
100                     ISO C89 compilers are required to support" when including
101                     the following XPM file... */
102 # endif
103 #include "../images/dymaxionmap.xpm"
104 #include "../images/ground.xpm"
105
106 #include "xpm-ximage.h"
107 #include "rotator.h"
108 #include "gltrackball.h"
109
110
111 typedef struct {
112   GLXContext *glx_context;
113   GLuint starlist;
114   int starcount;
115   rotator *rot;
116   trackball_state *trackball;
117   Bool button_down_p;
118   enum { STARTUP, FLAT, FOLD, 
119          ICO, STEL_IN, AXIS, SPIN, STEL, STEL_OUT,
120          ICO2, UNFOLD } state;
121   GLfloat ratio;
122   GLuint tex1, tex2;
123   texture_font_data *font_data;
124 } planetstruct;
125
126
127 static planetstruct *planets = NULL;
128
129
130 /* Set up and enable texturing on our object */
131 static void
132 setup_xpm_texture (ModeInfo *mi, char **xpm_data)
133 {
134   XImage *image = xpm_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
135                                   MI_COLORMAP (mi), xpm_data);
136   char buf[1024];
137   clear_gl_error();
138   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
139   /* iOS invalid enum:
140   glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
141   */
142   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
143                image->width, image->height, 0,
144                GL_RGBA,
145                /* GL_UNSIGNED_BYTE, */
146                GL_UNSIGNED_INT_8_8_8_8_REV,
147                image->data);
148   sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
149   check_gl_error(buf);
150 }
151
152
153 static Bool
154 setup_file_texture (ModeInfo *mi, char *filename)
155 {
156   Display *dpy = mi->dpy;
157   Visual *visual = mi->xgwa.visual;
158   char buf[1024];
159
160   Colormap cmap = mi->xgwa.colormap;
161   XImage *image = xpm_file_to_ximage (dpy, visual, cmap, filename);
162   if (!image) return False;
163
164   clear_gl_error();
165   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
166   glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
167   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
168                image->width, image->height, 0,
169                GL_RGBA,
170                /* GL_UNSIGNED_BYTE, */
171                GL_UNSIGNED_INT_8_8_8_8_REV,
172                image->data);
173   sprintf (buf, "texture: %.100s (%dx%d)",
174            filename, image->width, image->height);
175   check_gl_error(buf);
176   return True;
177 }
178
179
180 static void
181 setup_texture(ModeInfo * mi)
182 {
183   planetstruct *gp = &planets[MI_SCREEN(mi)];
184
185   glGenTextures (1, &gp->tex1);
186   glBindTexture (GL_TEXTURE_2D, gp->tex1);
187
188   /* Must be after glBindTexture */
189   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
190   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
191   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
192   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
193   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
194
195   if (!which_image ||
196       !*which_image ||
197       !strcmp(which_image, "BUILTIN"))
198     {
199     BUILTIN:
200       setup_xpm_texture (mi, dymaxionmap_xpm);
201     }
202   else
203     {
204       if (! setup_file_texture (mi, which_image))
205         goto BUILTIN;
206     }
207
208   glGenTextures (1, &gp->tex2);
209   glBindTexture (GL_TEXTURE_2D, gp->tex2);
210
211   /* Must be after glBindTexture */
212   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
213   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
214   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
215   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
216   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
217
218   setup_xpm_texture (mi, ground);
219
220   check_gl_error("texture initialization");
221
222   /* Need to flip the texture top for bottom for some reason. */
223   glMatrixMode (GL_TEXTURE);
224   glScalef (1, -1, 1);
225   glMatrixMode (GL_MODELVIEW);
226 }
227
228
229 static void
230 init_stars (ModeInfo *mi)
231 {
232   planetstruct *gp = &planets[MI_SCREEN(mi)];
233   int i, j;
234   int width  = MI_WIDTH(mi);
235   int height = MI_HEIGHT(mi);
236   int size = (width > height ? width : height);
237   int nstars = size * size / 80;
238   int max_size = 3;
239   GLfloat inc = 0.5;
240   int steps = max_size / inc;
241
242   gp->starlist = glGenLists(1);
243   glNewList(gp->starlist, GL_COMPILE);
244   for (j = 1; j <= steps; j++)
245     {
246       glPointSize(inc * j);
247       glBegin (GL_POINTS);
248       for (i = 0; i < nstars / steps; i++)
249         {
250           GLfloat d = 0.1;
251           GLfloat r = 0.15 + frand(0.3);
252           GLfloat g = r + frand(d) - d;
253           GLfloat b = r + frand(d) - d;
254
255           GLfloat x = frand(1)-0.5;
256           GLfloat y = frand(1)-0.5;
257           GLfloat z = ((random() & 1)
258                        ? frand(1)-0.5
259                        : (BELLRAND(1)-0.5)/12);   /* milky way */
260           d = sqrt (x*x + y*y + z*z);
261           x /= d;
262           y /= d;
263           z /= d;
264           glColor3f (r, g, b);
265           glVertex3f (x, y, z);
266           gp->starcount++;
267         }
268       glEnd ();
269     }
270   glEndList ();
271
272   check_gl_error("stars initialization");
273 }
274
275
276 ENTRYPOINT void
277 reshape_planet (ModeInfo *mi, int width, int height)
278 {
279   GLfloat h = (GLfloat) height / (GLfloat) width;
280
281   glViewport(0, 0, (GLint) width, (GLint) height);
282   glMatrixMode(GL_PROJECTION);
283   glLoadIdentity();
284   glFrustum(-1.0, 1.0, -h, h, 5.0, 100.0);
285   glMatrixMode(GL_MODELVIEW);
286   glLoadIdentity();
287   glTranslatef(0.0, 0.0, -40);
288
289 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
290   {
291     int o = (int) current_device_rotation();
292     if (o != 0 && o != 180 && o != -180)
293       glScalef (h, h, h);
294   }
295 # endif
296
297   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
298 }
299
300
301 static void
302 do_normal2 (Bool frontp, XYZ a, XYZ b, XYZ c)
303 {
304   if (frontp)
305     do_normal (a.x, a.y, a.z,
306                b.x, b.y, b.z,
307                c.x, c.y, c.z);
308   else
309     do_normal (b.x, b.y, b.z,
310                a.x, a.y, a.z,
311                c.x, c.y, c.z);
312 }
313
314
315 static void
316 triangle0 (ModeInfo *mi, Bool frontp, GLfloat stel_ratio, int bitmask)
317 {
318   /* Render a triangle as six sub-triangles:
319
320                 A
321                / \
322               / | \
323              /  |  \
324             / 0 | 1 \
325         E  /_   |   _\  F
326           /  \_ | _/  \
327          / 5   \D/   2 \
328         /    /  |  \    \
329        /   / 4  | 3  \   \
330       /  /      |       \ \
331    B ----------------------- C
332                 G
333    */
334
335   Bool wire = MI_IS_WIREFRAME(mi);
336   GLfloat h = sqrt(3) / 2;
337   GLfloat h2 = sqrt(h*h - (h/2)*(h/2)) - 0.5;
338   XYZ  A,  B,  C,  D,  E,  F,  G;
339   XYZ tA, tB, tC, tD, tE, tF, tG;
340   XYZ  a,  b,  c;
341   XYZ ta, tb, tc;
342   A.x =  0;   A.y = h;   A.z = 0;
343   B.x = -0.5, B.y = 0;   B.z = 0;
344   C.x =  0.5, C.y = 0;   C.z = 0;
345   D.x =  0;   D.y = h/3; D.z = 0;
346   E.x = -h2;  E.y = h/2; E.z = 0;
347   F.x =  h2;  F.y = h/2; F.z = 0;
348   G.x =  0;   G.y = 0;   G.z = 0;
349
350   /* When tweaking object XY to stellate, don't change texture coordinates. */
351   tA = A; tB = B; tC = C; tD = D; tE = E; tF = F; tG = G;
352
353   /* Eyeballed this to find the depth of stellation that seems to most
354      approximate a sphere.
355    */
356   D.z = 0.193 * stel_ratio;
357
358   /* We want to raise E, F and G as well but we can't just shift Z:
359      we need to keep them on the same vector from the center of the sphere,
360      which means also changing F and G's X and Y.
361   */
362   E.z = F.z = G.z = 0.132 * stel_ratio;
363   {
364     double magic_x = 0.044;
365     double magic_y = 0.028;
366     /* G.x stays 0 */
367     G.y -= sqrt (magic_x*magic_x + magic_y*magic_y) * stel_ratio;
368     E.x -= magic_x * stel_ratio;
369     E.y += magic_y * stel_ratio;
370     F.x += magic_x * stel_ratio;
371     F.y += magic_y * stel_ratio;
372   }
373
374
375   if (bitmask & 1<<0)
376     {
377       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
378       a  =  E;  b =  D;  c =  A;
379       ta = tE; tb = tD; tc = tA;
380       do_normal2 (frontp, a, b, c);
381       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
382       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
383       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
384       glEnd();
385       mi->polygon_count++;
386     }
387   if (bitmask & 1<<1)
388     {
389       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
390       a  =  D;  b =  F;  c =  A;
391       ta = tD; tb = tF; tc = tA;
392       do_normal2 (frontp, a, b, c);
393       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
394       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
395       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
396       glEnd();
397       mi->polygon_count++;
398     }
399   if (bitmask & 1<<2)
400     {
401       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
402       a  =  D;  b =  C;  c =  F;
403       ta = tD; tb = tC; tc = tF;
404       do_normal2 (frontp, a, b, c);
405       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
406       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
407       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
408       glEnd();
409       mi->polygon_count++;
410     }
411   if (bitmask & 1<<3)
412     {
413       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
414       a  =  G;  b =  C;  c =  D;
415       ta = tG; tb = tC; tc = tD;
416       do_normal2 (frontp, a, b, c);
417       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
418       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
419       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
420       glEnd();
421       mi->polygon_count++;
422     }
423   if (bitmask & 1<<4)
424     {
425       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
426       a  =  B;  b =  G;  c =  D;
427       ta = tB; tb = tG; tc = tD;
428       do_normal2 (frontp, a, b, c);
429       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
430       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
431       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
432       glEnd();
433       mi->polygon_count++;
434     }
435   if (bitmask & 1<<5)
436     {
437       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
438       a  =  B;  b =  D;  c =  E;
439       ta = tB; tb = tD; tc = tE;
440       do_normal2 (frontp, a, b, c);
441       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
442       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
443       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
444       glEnd();
445       mi->polygon_count++;
446     }
447   if (bitmask & 1<<6)
448     {
449       glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
450       a  =  E;  b =  D;  c =  A;
451       ta = tE; tb = tD; tc = tA;
452       do_normal2 (frontp, a, b, c);
453       glTexCoord2f (ta.x, ta.y); glVertex3f (a.x, a.y, a.z);
454       glTexCoord2f (tb.x, tb.y); glVertex3f (b.x, b.y, b.z);
455       glTexCoord2f (tc.x, tc.y); glVertex3f (c.x, c.y, c.z);
456       glEnd();
457       mi->polygon_count++;
458     }
459 }
460
461
462 /* The segments, numbered arbitrarily from the top left:
463              ________         _      ________
464              \      /\      /\ \    |\      /
465               \ 0  /  \    /  \3>   | \ 5  /
466                \  / 1  \  / 2  \| ..|4 \  /-6-..
467      ___________\/______\/______\/______\/______\
468     |   /\      /\      /\      /\      /\   
469     |7 /  \ 9  /  \ 11 /  \ 13 /  \ 15 /  \  
470     | / 8  \  / 10 \  / 12 \  / 14 \  / 16 \ 
471     |/______\/______\/______\/______\/______\
472      \      /\      /       /\      /\
473       \ 17 /  \ 18 /       /  \ 20 /  \
474        \  /    \  /       / 19 \  / 21 \
475         \/      \/       /______\/______\
476
477    Each triangle can be connected to at most two other triangles.
478    We start from the middle, #12, and work our way to the edges.
479    Its centroid is 0,0.
480  */
481 static void
482 triangle (ModeInfo *mi, int which, Bool frontp, 
483           GLfloat fold_ratio, GLfloat stel_ratio)
484 {
485   planetstruct *gp = &planets[MI_SCREEN(mi)];
486   const GLfloat fg[3] = { 1, 1, 1 };
487   const GLfloat bg[3] = { 0.3, 0.3, 0.3 };
488   int a = -1, b = -1;
489   GLfloat max = acos (sqrt(5)/3);
490   GLfloat rot = -max * fold_ratio / (M_PI/180);
491   Bool wire = MI_IS_WIREFRAME(mi);
492
493   if (wire)
494     glColor3fv (fg);
495
496   switch (which) {
497   case 3:                               /* One third of the face. */
498     triangle0 (mi, frontp, stel_ratio, 1<<3 | 1<<4);
499     break;
500   case 4:                               /* Two thirds of the face: convex. */
501     triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3 | 1<<4);
502     break;
503   case 6:                               /* One half of the face. */
504     triangle0 (mi, frontp, stel_ratio, 1<<1 | 1<<2 | 1<<3);
505     break;
506   case 7:                               /* One half of the face. */
507     triangle0 (mi, frontp, stel_ratio, 1<<2 | 1<<3 | 1<<4);
508     break;
509   default:                              /* Full face. */
510     triangle0 (mi, frontp, stel_ratio, 0x3F);
511     break;
512   }
513
514   if (wire)
515     {
516       char tag[20];
517       glColor3fv (bg);
518       sprintf (tag, "%d", which);
519       glPushMatrix();
520       glTranslatef (-0.1, 0.2, 0);
521       glScalef (0.005, 0.005, 0.005);
522       print_texture_string (gp->font_data, tag);
523       glPopMatrix();
524       mi->polygon_count++;
525     }
526
527
528   /* The connection hierarchy of the faces starting at the middle, #12. */
529   switch (which) {
530   case  0: break;
531   case  1: a =  0; b = -1; break;
532   case  2: a = -1; b =  3; break;
533   case  3: break;
534   case  4: a = -1; b =  5; break;
535   case  5: a = -1; b =  6; break;
536   case  7: break;
537   case  6: break;
538   case  8: a = 17; b =  7; break;
539   case  9: a =  8; b = -1; break;
540   case 10: a = 18; b =  9; break;
541   case 11: a = 10; b =  1; break;
542   case 12: a = 11; b = 13; break;
543   case 13: a =  2; b = 14; break;
544   case 14: a = 15; b = 20; break;
545   case 15: a =  4; b = 16; break;
546   case 16: break;
547   case 17: break;
548   case 18: break;
549   case 19: break;
550   case 20: a = 21; b = 19; break;
551   case 21: break;
552   default: abort(); break;
553   }
554
555   if (a != -1)
556     {
557       glPushMatrix();
558       glTranslatef (-0.5, 0, 0);        /* Move model matrix to upper left */
559       glRotatef (60, 0, 0, 1);
560       glTranslatef ( 0.5, 0, 0);
561
562       glMatrixMode(GL_TEXTURE);
563       /* glPushMatrix(); */
564       glTranslatef (-0.5, 0, 0);        /* Move texture matrix the same way */
565       glRotatef (60, 0, 0, 1);
566       glTranslatef ( 0.5, 0, 0);
567
568       glMatrixMode(GL_MODELVIEW);
569
570       glRotatef (rot, 1, 0, 0);
571       triangle (mi, a, frontp, fold_ratio, stel_ratio);
572
573       /* This should just be a PopMatrix on the TEXTURE stack, but
574          fucking iOS has GL_MAX_TEXTURE_STACK_DEPTH == 4!  WTF!
575          So we have to undo our rotations and translations manually.
576        */
577       glMatrixMode(GL_TEXTURE);
578       /* glPopMatrix(); */
579       glTranslatef (-0.5, 0, 0);
580       glRotatef (-60, 0, 0, 1);
581       glTranslatef (0.5, 0, 0);
582
583       glMatrixMode(GL_MODELVIEW);
584       glPopMatrix();
585     }
586
587   if (b != -1)
588     {
589       glPushMatrix();
590       glTranslatef (0.5, 0, 0);         /* Move model matrix to upper right */
591       glRotatef (-60, 0, 0, 1);
592       glTranslatef (-0.5, 0, 0);
593
594       glMatrixMode(GL_TEXTURE);
595       /* glPushMatrix(); */
596       glTranslatef (0.5, 0, 0);         /* Move texture matrix the same way */
597       glRotatef (-60, 0, 0, 1);
598       glTranslatef (-0.5, 0, 0);
599
600       glMatrixMode(GL_MODELVIEW);
601
602       glRotatef (rot, 1, 0, 0);
603       triangle (mi, b, frontp, fold_ratio, stel_ratio);
604
605       /* See above. Grr. */
606       glMatrixMode(GL_TEXTURE);
607       /* glPopMatrix(); */
608       glTranslatef (0.5, 0, 0);
609       glRotatef (60, 0, 0, 1);
610       glTranslatef (-0.5, 0, 0);
611
612       glMatrixMode(GL_MODELVIEW);
613       glPopMatrix();
614     }
615 }
616
617
618 static void
619 draw_triangles (ModeInfo *mi, GLfloat fold_ratio, GLfloat stel_ratio)
620 {
621   planetstruct *gp = &planets[MI_SCREEN(mi)];
622   Bool wire = MI_IS_WIREFRAME(mi);
623   GLfloat h = sqrt(3) / 2;
624   GLfloat c = h / 3;
625
626   glTranslatef (0, -h/3, 0);  /* Center on face 12 */
627
628   /* When closed, center on midpoint of icosahedron. Eyeballed this. */
629   glTranslatef (0, 0, fold_ratio * 0.754);
630
631   glFrontFace (GL_CCW);
632
633   /* Adjust the texture matrix so that it has the same coordinate space
634      as the model. */
635
636   glMatrixMode(GL_TEXTURE);
637   glPushMatrix();
638   {
639     GLfloat texw = 5.5;
640     GLfloat texh = 3 * h;
641     GLfloat midx = 2.5;
642     GLfloat midy = 3 * c;
643     glScalef (1/texw, -1/texh, 1);
644     glTranslatef (midx, midy, 0);
645   }
646   glMatrixMode(GL_MODELVIEW);
647
648
649
650   /* Front faces */
651
652   if (wire)
653     glDisable (GL_TEXTURE_2D);
654   else if (do_texture)
655     {
656       glEnable (GL_TEXTURE_2D);
657       glBindTexture (GL_TEXTURE_2D, gp->tex1);
658     }
659   else
660     glDisable (GL_TEXTURE_2D);
661
662   triangle (mi, 12, True, fold_ratio, stel_ratio);
663
664   /* Back faces */
665
666   if (wire)
667     glDisable (GL_TEXTURE_2D);
668   else if (do_texture)
669     {
670       glEnable (GL_TEXTURE_2D);
671       glBindTexture (GL_TEXTURE_2D, gp->tex2);
672     }
673   else
674     glDisable (GL_TEXTURE_2D);
675
676   glFrontFace (GL_CW);
677
678   triangle (mi, 12, False, fold_ratio, 0);
679
680   glMatrixMode(GL_TEXTURE);
681   glPopMatrix();
682   glMatrixMode(GL_MODELVIEW);
683 }
684
685
686 static void
687 align_axis (ModeInfo *mi, int undo)
688 {
689   /* Rotate so that an axis is lined up with the north and south poles
690      on the map, which are not in the center of their faces, or any
691      other easily computable spot. */
692
693   GLfloat r1 = 20.5;
694   GLfloat r2 = 28.5;
695
696   if (undo)
697     {
698       glRotatef (-r2, 0, 1, 0);
699       glRotatef ( r2, 1, 0, 0);
700       glRotatef (-r1, 1, 0, 0);
701     }
702   else
703     {
704       glRotatef (r1, 1, 0, 0);
705       glRotatef (-r2, 1, 0, 0);
706       glRotatef ( r2, 0, 1, 0);
707     }
708 }
709
710
711 static void
712 draw_axis (ModeInfo *mi)
713 {
714   GLfloat s;
715   glDisable (GL_TEXTURE_2D);
716   glDisable (GL_LIGHTING);
717   glPushMatrix();
718
719   align_axis (mi, 0);
720   glTranslatef (0.34, 0.39, -0.61);
721
722   s = 0.96;
723   glScalef (s, s, s);   /* tighten up the enclosing sphere */
724
725   glColor3f (0.5, 0.5, 0);
726
727   glRotatef (90,  1, 0, 0);    /* unit_sphere is off by 90 */
728   glRotatef (9.5, 0, 1, 0);    /* line up the time zones */
729   glFrontFace (GL_CCW);
730   unit_sphere (12, 24, True);
731   glBegin(GL_LINES);
732   glVertex3f(0, -2, 0);
733   glVertex3f(0,  2, 0);
734   glEnd();
735
736   glPopMatrix();
737 }
738
739
740
741
742 ENTRYPOINT Bool
743 planet_handle_event (ModeInfo *mi, XEvent *event)
744 {
745   planetstruct *gp = &planets[MI_SCREEN(mi)];
746
747   if (gltrackball_event_handler (event, gp->trackball,
748                                  MI_WIDTH (mi), MI_HEIGHT (mi),
749                                  &gp->button_down_p))
750     return True;
751   else if (event->xany.type == KeyPress)
752     {
753       KeySym keysym;
754       char c = 0;
755       XLookupString (&event->xkey, &c, 1, &keysym, 0);
756       if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
757         {
758           switch (gp->state) {
759           case FLAT: case ICO: case STEL: case AXIS: case ICO2:
760             gp->ratio = 1;
761             break;
762           default:
763             break;
764           }
765           return True;
766         }
767     }
768
769   return False;
770 }
771
772
773 static void free_planet (ModeInfo * mi);
774
775
776 ENTRYPOINT void
777 init_planet (ModeInfo * mi)
778 {
779   planetstruct *gp;
780   int screen = MI_SCREEN(mi);
781   Bool wire = MI_IS_WIREFRAME(mi);
782
783   MI_INIT (mi, planets, free_planet);
784   gp = &planets[screen];
785
786   if ((gp->glx_context = init_GL(mi)) != NULL) {
787     reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
788   }
789
790   gp->state = STARTUP;
791   gp->ratio = 0;
792   gp->font_data = load_texture_font (mi->dpy, "labelFont");
793
794   {
795     double spin_speed   = 0.1;
796     double wander_speed = 0.002;
797     gp->rot = make_rotator (do_roll ? spin_speed : 0,
798                             do_roll ? spin_speed : 0,
799                             0, 1,
800                             do_wander ? wander_speed : 0,
801                             False);
802     gp->trackball = gltrackball_init (True);
803   }
804
805   if (wire)
806     do_texture = False;
807
808   if (do_texture)
809     setup_texture (mi);
810
811   if (do_stars)
812     init_stars (mi);
813
814   glEnable (GL_DEPTH_TEST);
815   glEnable (GL_NORMALIZE);
816   glEnable (GL_CULL_FACE);
817
818   if (!wire)
819     {
820       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
821       GLfloat amb[4] = {0.4, 0.4, 0.4, 1.0};
822       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
823       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
824
825       glEnable(GL_LIGHTING);
826       glEnable(GL_LIGHT0);
827       glLightfv(GL_LIGHT0, GL_POSITION, pos);
828       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
829       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
830       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
831     }
832 }
833
834
835 static GLfloat
836 ease_fn (GLfloat r)
837 {
838   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
839 }
840
841
842 static GLfloat
843 ease_ratio (GLfloat r)
844 {
845   GLfloat ease = 0.35;
846   if      (r <= 0)     return 0;
847   else if (r >= 1)     return 1;
848   else if (r <= ease)  return     ease * ease_fn (r / ease);
849   else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
850   else                 return r;
851 }
852
853
854 ENTRYPOINT void
855 draw_planet (ModeInfo * mi)
856 {
857   planetstruct *gp = &planets[MI_SCREEN(mi)];
858   int wire = MI_IS_WIREFRAME(mi);
859   Display *dpy = MI_DISPLAY(mi);
860   Window window = MI_WINDOW(mi);
861   double x, y, z;
862
863   if (!gp->glx_context)
864     return;
865
866   glDrawBuffer(GL_BACK);
867   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
868
869   glXMakeCurrent (dpy, window, *(gp->glx_context));
870
871   mi->polygon_count = 0;
872
873   if (! gp->button_down_p)
874     switch (gp->state) {
875     case STARTUP:  gp->ratio += speed * 0.01;  break;
876     case FLAT:     gp->ratio += speed * 0.005; break;
877     case FOLD:     gp->ratio += speed * 0.01;  break;
878     case ICO:      gp->ratio += speed * 0.01;  break;
879     case STEL_IN:  gp->ratio += speed * 0.05;  break;
880     case STEL:     gp->ratio += speed * 0.01;  break;
881     case STEL_OUT: gp->ratio += speed * 0.07;  break;
882     case ICO2:     gp->ratio += speed * 0.07;  break;
883     case AXIS:     gp->ratio += speed * 0.02;  break;
884     case SPIN:     gp->ratio += speed * 0.005; break;
885     case UNFOLD:   gp->ratio += speed * 0.01;  break;
886     default:       abort();
887     }
888
889   if (gp->ratio > 1.0)
890     {
891       gp->ratio = 0;
892       switch (gp->state) {
893       case STARTUP:  gp->state = FLAT;     break;
894       case FLAT:     gp->state = FOLD;     break;
895       case FOLD:     gp->state = ICO;      break;
896       case ICO:      gp->state = STEL_IN;  break;
897       case STEL_IN:  gp->state = STEL;     break;
898       case STEL:
899         {
900           int i = (random() << 9) % 7;
901           gp->state = (i < 3 ? STEL_OUT :
902                        i < 6 ? SPIN : AXIS);
903         }
904         break;
905       case AXIS:     gp->state = STEL_OUT; break;
906       case SPIN:     gp->state = STEL_OUT; break;
907       case STEL_OUT: gp->state = ICO2;     break;
908       case ICO2:     gp->state = UNFOLD;   break;
909       case UNFOLD:   gp->state = FLAT;     break;
910       default:       abort();
911       }
912     }
913
914   glEnable(GL_LINE_SMOOTH);
915   glEnable(GL_POINT_SMOOTH);
916   glEnable(GL_DEPTH_TEST);
917   glEnable(GL_CULL_FACE);
918   glCullFace(GL_BACK); 
919
920   glPushMatrix();
921
922   gltrackball_rotate (gp->trackball);
923   glRotatef (current_device_rotation(), 0, 0, 1);
924
925   if (gp->state != STARTUP)
926     {
927       get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
928       x = (x - 0.5) * 3;
929       y = (y - 0.5) * 3;
930       z = 0;
931       glTranslatef(x, y, z);
932     }
933
934   if (do_roll && gp->state != STARTUP)
935     {
936       get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
937       glRotatef (x * 360, 1.0, 0.0, 0.0);
938       glRotatef (y * 360, 0.0, 1.0, 0.0);
939     }
940
941   if (do_stars)
942     {
943       glDisable(GL_TEXTURE_2D);
944       glDisable(GL_LIGHTING);
945       glPushMatrix();
946       glScalef (60, 60, 60);
947       glRotatef (90, 1, 0, 0);
948       glRotatef (35, 1, 0, 0);
949       glCallList (gp->starlist);
950       mi->polygon_count += gp->starcount;
951       glPopMatrix();
952       glClear(GL_DEPTH_BUFFER_BIT);
953     }
954
955   if (! wire)
956     glEnable (GL_LIGHTING);
957
958   if (do_texture)
959     glEnable(GL_TEXTURE_2D);
960
961   glScalef (2.6, 2.6, 2.6);
962
963   {
964     GLfloat fold_ratio = 0;
965     GLfloat stel_ratio = 0;
966     switch (gp->state) {
967     case FOLD:     fold_ratio =     gp->ratio; break;
968     case UNFOLD:   fold_ratio = 1 - gp->ratio; break;
969     case ICO: case ICO2: fold_ratio = 1; break;
970     case STEL: case AXIS: case SPIN: fold_ratio = 1; stel_ratio = 1; break;
971     case STEL_IN:  fold_ratio = 1; stel_ratio = gp->ratio; break;
972     case STEL_OUT: fold_ratio = 1; stel_ratio = 1 - gp->ratio; break;
973     case STARTUP:      /* Tilt in from flat */
974       glRotatef (-90 * ease_ratio (1 - gp->ratio), 1, 0, 0);
975       break;
976
977     default: break;
978     }
979
980 # ifdef HAVE_MOBILE  /* Enlarge the icosahedron a bit to make it more visible */
981     {
982       GLfloat s = 1 + 1.3 * ease_ratio (fold_ratio);
983       glScalef (s, s, s);
984     }
985 # endif
986
987     if (gp->state == SPIN)
988       {
989         align_axis (mi, 0);
990         glRotatef (ease_ratio (gp->ratio) * 360 * 3, 0, 0, 1);
991         align_axis (mi, 1);
992       }
993
994     draw_triangles (mi, ease_ratio (fold_ratio), ease_ratio (stel_ratio));
995
996     if (gp->state == AXIS)
997       draw_axis(mi);
998   }
999
1000   glPopMatrix();
1001
1002   if (mi->fps_p) do_fps (mi);
1003   glFinish();
1004   glXSwapBuffers(dpy, window);
1005 }
1006
1007
1008 static void
1009 free_planet (ModeInfo * mi)
1010 {
1011   planetstruct *gp = &planets[MI_SCREEN(mi)];
1012
1013   if (gp->glx_context) {
1014     glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(gp->glx_context));
1015
1016     if (glIsList(gp->starlist))
1017       glDeleteLists(gp->starlist, 1);
1018   }
1019 }
1020
1021
1022 XSCREENSAVER_MODULE_2 ("DymaxionMap", dymaxionmap, planet)
1023
1024 #endif