1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* glplanet --- 3D rotating planet, e.g., Earth.
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.
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.
18 * 10-Nov-14: jwz@jwz.org Night map. Better stars.
19 * 16-Jan-02: jwz@jwz.org gdk_pixbuf support.
20 * 21-Mar-01: jwz@jwz.org Broke sphere routine out into its own file.
22 * 9-Oct-98: dek@cgl.ucsf.edu Added stars.
24 * 8-Oct-98: jwz@jwz.org Made the 512x512x1 xearth image be built in.
25 * Made it possible to load XPM or XBM files.
26 * Made the planet bounce and roll around.
28 * 8-Oct-98: Released initial version of "glplanet"
29 * (David Konerding, dek@cgl.ucsf.edu)
34 #define DEFAULTS "*delay: 20000 \n" \
35 "*showFPS: False \n" \
36 "*wireframe: False \n" \
37 "*imageForeground: Green \n" \
38 "*imageBackground: Blue \n" \
39 "*suppressRotationAnimation: True\n" \
41 # define release_planet 0
42 # include "xlockmore.h" /* from the xscreensaver distribution */
43 #else /* !STANDALONE */
44 # include "xlock.h" /* from the xlockmore distribution */
45 #endif /* !STANDALONE */
47 #ifdef USE_GL /* whole file */
53 # include <X11/Xmu/Drawing.h>
55 # include <Xmu/Drawing.h>
59 #define DEF_ROTATE "True"
60 #define DEF_ROLL "True"
61 #define DEF_WANDER "True"
62 #define DEF_SPIN "0.03"
63 #define DEF_TEXTURE "True"
64 #define DEF_STARS "True"
65 #define DEF_RESOLUTION "128"
66 #define DEF_IMAGE "BUILTIN"
67 #define DEF_IMAGE2 "BUILTIN"
70 #define countof(x) (sizeof((x))/sizeof((*x)))
73 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
78 static int do_texture;
80 static char *which_image;
81 static char *which_image2;
82 static int resolution;
84 static XrmOptionDescRec opts[] = {
85 {"-rotate", ".glplanet.rotate", XrmoptionNoArg, "true" },
86 {"+rotate", ".glplanet.rotate", XrmoptionNoArg, "false" },
87 {"-roll", ".glplanet.roll", XrmoptionNoArg, "true" },
88 {"+roll", ".glplanet.roll", XrmoptionNoArg, "false" },
89 {"-wander", ".glplanet.wander", XrmoptionNoArg, "true" },
90 {"+wander", ".glplanet.wander", XrmoptionNoArg, "false" },
91 {"-texture", ".glplanet.texture", XrmoptionNoArg, "true" },
92 {"+texture", ".glplanet.texture", XrmoptionNoArg, "false" },
93 {"-stars", ".glplanet.stars", XrmoptionNoArg, "true" },
94 {"+stars", ".glplanet.stars", XrmoptionNoArg, "false" },
95 {"-spin", ".glplanet.spin", XrmoptionSepArg, 0 },
96 {"-image", ".glplanet.image", XrmoptionSepArg, 0 },
97 {"-image2", ".glplanet.image2", XrmoptionSepArg, 0 },
98 {"-resolution", ".glplanet.resolution", XrmoptionSepArg, 0 },
101 static argtype vars[] = {
102 {&do_rotate, "rotate", "Rotate", DEF_ROTATE, t_Bool},
103 {&do_roll, "roll", "Roll", DEF_ROLL, t_Bool},
104 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
105 {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool},
106 {&do_stars, "stars", "Stars", DEF_STARS, t_Bool},
107 {&which_image, "image", "Image", DEF_IMAGE, t_String},
108 {&which_image2,"image2", "Image", DEF_IMAGE2, t_String},
109 {&resolution, "resolution","Resolution", DEF_RESOLUTION, t_Int},
112 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
115 ModStruct planet_description =
116 {"planet", "init_planet", "draw_planet", NULL,
117 "draw_planet", "init_planet", "free_planet", &planet_opts,
118 1000, 1, 2, 1, 4, 1.0, "",
119 "Animates texture mapped sphere (planet)", 0, NULL};
122 #include "images/gen/earth_png.h"
123 #include "images/gen/earth_night_png.h"
125 #include "ximage-loader.h"
127 #include "gltrackball.h"
131 * slices and stacks are used in the sphere parameterization routine.
132 * more slices and stacks will increase the quality of the sphere,
133 * at the expense of rendering speed
136 /* structure for holding the planet data */
143 int screen_width, screen_height;
144 GLXContext *glx_context;
150 trackball_state *trackball;
158 static planetstruct *planets = NULL;
161 /* Set up and enable texturing on our object */
163 setup_xpm_texture (ModeInfo *mi, const unsigned char *data, unsigned long size)
165 XImage *image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
169 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
171 glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
173 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
174 image->width, image->height, 0,
175 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
176 sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
182 setup_file_texture (ModeInfo *mi, char *filename)
184 Display *dpy = mi->dpy;
185 Visual *visual = mi->xgwa.visual;
188 XImage *image = file_to_ximage (dpy, visual, filename);
189 if (!image) return False;
192 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
193 glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
194 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
195 image->width, image->height, 0,
196 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
197 sprintf (buf, "texture: %.100s (%dx%d)",
198 filename, image->width, image->height);
205 setup_texture(ModeInfo * mi)
207 planetstruct *gp = &planets[MI_SCREEN(mi)];
209 glGenTextures (1, &gp->tex1);
210 glBindTexture (GL_TEXTURE_2D, gp->tex1);
212 /* Must be after glBindTexture */
213 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
214 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
215 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
216 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
217 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
221 !strcmp(which_image, "BUILTIN"))
224 setup_xpm_texture (mi, earth_png, sizeof(earth_png));
228 if (! setup_file_texture (mi, which_image))
232 check_gl_error("texture 1 initialization");
234 glGenTextures (1, &gp->tex2);
235 glBindTexture (GL_TEXTURE_2D, gp->tex2);
237 /* Must be after glBindTexture */
238 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
239 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
240 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
241 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
242 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
246 !strcmp(which_image2, "BUILTIN"))
249 setup_xpm_texture (mi, earth_night_png, sizeof(earth_night_png));
253 if (! setup_file_texture (mi, which_image2))
257 check_gl_error("texture 2 initialization");
259 /* Need to flip the texture top for bottom for some reason. */
260 glMatrixMode (GL_TEXTURE);
262 glMatrixMode (GL_MODELVIEW);
267 init_stars (ModeInfo *mi)
269 planetstruct *gp = &planets[MI_SCREEN(mi)];
271 int width = MI_WIDTH(mi);
272 int height = MI_HEIGHT(mi);
273 int size = (width > height ? width : height);
274 int nstars = size * size / 80;
277 int steps = max_size / inc;
280 if (MI_WIDTH(mi) > 2560) { /* Retina displays */
285 gp->starlist = glGenLists(1);
286 glNewList(gp->starlist, GL_COMPILE);
287 for (j = 1; j <= steps; j++)
289 glPointSize(inc * j * scale);
291 for (i = 0; i < nstars / steps; i++)
294 GLfloat r = 0.15 + frand(0.3);
295 GLfloat g = r + frand(d) - d;
296 GLfloat b = r + frand(d) - d;
298 GLfloat x = frand(1)-0.5;
299 GLfloat y = frand(1)-0.5;
300 GLfloat z = ((random() & 1)
302 : (BELLRAND(1)-0.5)/12); /* milky way */
303 d = sqrt (x*x + y*y + z*z);
308 glVertex3f (x, y, z);
315 check_gl_error("stars initialization");
320 reshape_planet (ModeInfo *mi, int width, int height)
322 planetstruct *gp = &planets[MI_SCREEN(mi)];
323 GLfloat h = (GLfloat) height / (GLfloat) width;
325 glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
327 glViewport(0, 0, (GLint) width, (GLint) height);
328 glMatrixMode(GL_PROJECTION);
330 glFrustum(-1.0, 1.0, -h, h, 5.0, 200.0);
331 glMatrixMode(GL_MODELVIEW);
333 glTranslatef(0.0, 0.0, -40);
335 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
337 int o = (int) current_device_rotation();
338 if (o != 0 && o != 180 && o != -180)
343 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
348 planet_handle_event (ModeInfo *mi, XEvent *event)
350 planetstruct *gp = &planets[MI_SCREEN(mi)];
352 if (gltrackball_event_handler (event, gp->trackball,
353 MI_WIDTH (mi), MI_HEIGHT (mi),
362 init_planet (ModeInfo * mi)
365 int screen = MI_SCREEN(mi);
366 Bool wire = MI_IS_WIREFRAME(mi);
368 MI_INIT (mi, planets);
369 gp = &planets[screen];
371 gp->window = MI_WINDOW(mi);
373 if ((gp->glx_context = init_GL(mi)) != NULL) {
374 reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
378 char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
379 char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
381 if (!f) f = strdup("white");
382 if (!b) b = strdup("black");
384 for (s = f + strlen(f)-1; s > f; s--)
385 if (*s == ' ' || *s == '\t')
387 for (s = b + strlen(b)-1; s > b; s--)
388 if (*s == ' ' || *s == '\t')
391 if (!XParseColor(mi->dpy, mi->xgwa.colormap, f, &gp->fg))
393 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
396 if (!XParseColor(mi->dpy, mi->xgwa.colormap, b, &gp->bg))
398 fprintf(stderr, "%s: unparsable color: \"%s\"\n", progname, f);
407 double spin_speed = 0.1;
408 double wander_speed = 0.005;
409 gp->rot = make_rotator (do_roll ? spin_speed : 0,
410 do_roll ? spin_speed : 0,
412 do_wander ? wander_speed : 0,
415 gp->tilt = frand (23.4);
416 gp->trackball = gltrackball_init (True);
428 /* construct the polygons of the planet
430 gp->platelist = glGenLists(1);
431 glNewList (gp->platelist, GL_COMPILE);
434 glRotatef (90, 1, 0, 0);
435 unit_sphere (resolution, resolution, wire);
439 gp->shadowlist = glGenLists(1);
440 glNewList (gp->shadowlist, GL_COMPILE);
442 unit_dome (resolution, resolution, wire);
445 /* construct the polygons of the latitude/longitude/axis lines.
447 gp->latlonglist = glGenLists(1);
448 glNewList (gp->latlonglist, GL_COMPILE);
450 glRotatef (90, 1, 0, 0); /* unit_sphere is off by 90 */
451 glRotatef (8, 0, 1, 0); /* line up the time zones */
452 unit_sphere (12, 24, 1);
453 unit_sphere (12, 24, 1);
455 glVertex3f(0, -2, 0);
464 draw_planet (ModeInfo * mi)
466 planetstruct *gp = &planets[MI_SCREEN(mi)];
467 int wire = MI_IS_WIREFRAME(mi);
468 Display *dpy = MI_DISPLAY(mi);
469 Window window = MI_WINDOW(mi);
472 if (!gp->glx_context)
475 glDrawBuffer(GL_BACK);
476 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
478 glXMakeCurrent (dpy, window, *(gp->glx_context));
480 mi->polygon_count = 0;
482 if (gp->button_down_p)
484 else if (!gp->draw_axis && !(random() % 1000))
485 gp->draw_axis = 60 + (random() % 90);
487 if (do_rotate && !gp->button_down_p)
489 gp->z -= 0.001; /* the sun sets in the west */
490 if (gp->z < 0) gp->z += 1;
493 glEnable(GL_LINE_SMOOTH);
494 glEnable(GL_POINT_SMOOTH);
495 glEnable(GL_DEPTH_TEST);
496 glEnable(GL_CULL_FACE);
501 get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
505 glTranslatef(x, y, z);
507 gltrackball_rotate (gp->trackball);
511 get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
512 glRotatef (x * 360, 1.0, 0.0, 0.0);
513 glRotatef (y * 360, 0.0, 1.0, 0.0);
516 glRotatef (current_device_rotation(), 0, 0, 1);
520 glDisable(GL_TEXTURE_2D);
522 glScalef (60, 60, 60);
523 glRotatef (90, 1, 0, 0);
524 glRotatef (35, 1, 0, 0);
525 glCallList (gp->starlist);
526 mi->polygon_count += gp->starcount;
528 glClear(GL_DEPTH_BUFFER_BIT);
531 glRotatef (90, 1, 0, 0);
532 glRotatef (35, 1, 0, 0);
533 glRotatef (10, 0, 1, 0);
534 glRotatef (120, 0, 0, 1);
543 glColor3f (0.5, 0.5, 1);
549 glEnable(GL_TEXTURE_2D);
550 glBindTexture (GL_TEXTURE_2D, gp->tex1);
554 glRotatef (gp->z * 360, 0, 0, 1);
555 glCallList (gp->platelist);
556 mi->polygon_count += resolution*resolution;
559 /* Originally we just used GL_LIGHT0 to produce the day/night sides of
560 the planet, but that always looked crappy, even with a vast number of
561 polygons, because the day/night terminator didn't exactly line up with
564 So instead, draw the full "day" sphere; clear the depth buffer; draw
565 a rotated/tilted half-sphere into the depth buffer only; then draw
566 the "night" sphere. That lets us divide the sphere into the two maps,
567 and the dividing line can be at any angle, regardless of polygon layout.
569 The half-sphere is scaled slightly larger to avoid polygon fighting,
570 since those triangles won't exactly line up because of the rotation.
572 The downside of this is that the day/night terminator is 100% sharp.
573 It would be nice if it was a little blurry.
579 glRotatef (gp->tilt, 1, 0, 0);
582 glCallList (gp->shadowlist);
584 mi->polygon_count += resolution*(resolution/2);
587 else if (do_texture && gp->tex2)
589 glClear(GL_DEPTH_BUFFER_BIT);
590 glDisable(GL_TEXTURE_2D);
591 glColorMask (0, 0, 0, 0);
593 glRotatef (gp->tilt, 1, 0, 0);
594 glScalef (1.01, 1.01, 1.01);
595 glCallList (gp->shadowlist);
596 mi->polygon_count += resolution*(resolution/2);
598 glColorMask (1, 1, 1, 1);
599 glEnable(GL_TEXTURE_2D);
601 glBindTexture (GL_TEXTURE_2D, gp->tex2);
603 glRotatef (gp->z * 360, 0, 0, 1);
604 glCallList (gp->platelist);
605 mi->polygon_count += resolution*resolution;
612 glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
613 glScalef (1.02, 1.02, 1.02);
614 glDisable (GL_TEXTURE_2D);
615 glDisable (GL_LIGHTING);
616 glDisable (GL_LINE_SMOOTH);
617 glColor3f (0.1, 0.3, 0.1);
618 glCallList (gp->latlonglist);
619 mi->polygon_count += 24*24;
621 if (gp->draw_axis) gp->draw_axis--;
625 if (mi->fps_p) do_fps (mi);
627 glXSwapBuffers(dpy, window);
632 free_planet (ModeInfo * mi)
634 planetstruct *gp = &planets[MI_SCREEN(mi)];
636 if (gp->glx_context) {
637 glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
639 if (glIsList(gp->platelist))
640 glDeleteLists(gp->platelist, 1);
641 if (glIsList(gp->starlist))
642 glDeleteLists(gp->starlist, 1);
647 XSCREENSAVER_MODULE_2 ("GLPlanet", glplanet, planet)