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