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 "1.0"
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"
69 #define BLENDED_TERMINATOR
72 #define countof(x) (sizeof((x))/sizeof((*x)))
75 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
80 static int do_texture;
82 static char *which_image;
83 static char *which_image2;
84 static int resolution;
85 static GLfloat spin_arg;
87 static XrmOptionDescRec opts[] = {
88 {"-rotate", ".rotate", XrmoptionNoArg, "true" },
89 {"+rotate", ".rotate", XrmoptionNoArg, "false" },
90 {"-roll", ".roll", XrmoptionNoArg, "true" },
91 {"+roll", ".roll", XrmoptionNoArg, "false" },
92 {"-wander", ".wander", XrmoptionNoArg, "true" },
93 {"+wander", ".wander", XrmoptionNoArg, "false" },
94 {"-texture", ".texture", XrmoptionNoArg, "true" },
95 {"+texture", ".texture", XrmoptionNoArg, "false" },
96 {"-stars", ".stars", XrmoptionNoArg, "true" },
97 {"+stars", ".stars", XrmoptionNoArg, "false" },
98 {"-spin", ".spin", XrmoptionSepArg, 0 },
99 {"-image", ".image", XrmoptionSepArg, 0 },
100 {"-image2", ".image2", XrmoptionSepArg, 0 },
101 {"-resolution", ".resolution", XrmoptionSepArg, 0 },
104 static argtype vars[] = {
105 {&do_rotate, "rotate", "Rotate", DEF_ROTATE, t_Bool},
106 {&do_roll, "roll", "Roll", DEF_ROLL, t_Bool},
107 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
108 {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool},
109 {&do_stars, "stars", "Stars", DEF_STARS, t_Bool},
110 {&spin_arg, "spin", "Spin", DEF_SPIN, t_Float},
111 {&which_image, "image", "Image", DEF_IMAGE, t_String},
112 {&which_image2,"image2", "Image", DEF_IMAGE2, t_String},
113 {&resolution, "resolution","Resolution", DEF_RESOLUTION, t_Int},
116 ENTRYPOINT ModeSpecOpt planet_opts = {countof(opts), opts, countof(vars), vars, NULL};
119 ModStruct planet_description =
120 {"planet", "init_planet", "draw_planet", NULL,
121 "draw_planet", "init_planet", "free_planet", &planet_opts,
122 1000, 1, 2, 1, 4, 1.0, "",
123 "Animates texture mapped sphere (planet)", 0, NULL};
126 #include "images/gen/earth_png.h"
127 #include "images/gen/earth_night_png.h"
129 #include "ximage-loader.h"
131 #include "gltrackball.h"
135 * slices and stacks are used in the sphere parameterization routine.
136 * more slices and stacks will increase the quality of the sphere,
137 * at the expense of rendering speed
140 /* structure for holding the planet data */
147 int screen_width, screen_height;
148 GLXContext *glx_context;
153 trackball_state *trackball;
161 static planetstruct *planets = NULL;
164 /* Set up and enable texturing on our object */
166 setup_xpm_texture (ModeInfo *mi, const unsigned char *data, unsigned long size)
168 XImage *image = image_data_to_ximage (MI_DISPLAY (mi), MI_VISUAL (mi),
172 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
174 glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
176 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
177 image->width, image->height, 0,
178 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
179 sprintf (buf, "builtin texture (%dx%d)", image->width, image->height);
185 setup_file_texture (ModeInfo *mi, char *filename)
187 Display *dpy = mi->dpy;
188 Visual *visual = mi->xgwa.visual;
191 XImage *image = file_to_ximage (dpy, visual, filename);
192 if (!image) return False;
195 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
196 glPixelStorei(GL_UNPACK_ROW_LENGTH, image->width);
197 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
198 image->width, image->height, 0,
199 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
200 sprintf (buf, "texture: %.100s (%dx%d)",
201 filename, image->width, image->height);
208 setup_texture (ModeInfo * mi)
210 planetstruct *gp = &planets[MI_SCREEN(mi)];
212 glGenTextures (1, &gp->tex1);
213 glBindTexture (GL_TEXTURE_2D, gp->tex1);
215 /* Must be after glBindTexture */
216 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
217 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
218 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
219 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
220 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
224 !strcmp(which_image, "BUILTIN"))
227 setup_xpm_texture (mi, earth_png, sizeof(earth_png));
231 if (! setup_file_texture (mi, which_image))
235 check_gl_error("texture 1 initialization");
237 glGenTextures (1, &gp->tex2);
238 glBindTexture (GL_TEXTURE_2D, gp->tex2);
240 /* Must be after glBindTexture */
241 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
242 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
243 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
244 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
245 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
249 !strcmp(which_image2, "BUILTIN"))
252 setup_xpm_texture (mi, earth_night_png, sizeof(earth_night_png));
256 if (! setup_file_texture (mi, which_image2))
260 check_gl_error("texture 2 initialization");
262 /* Need to flip the texture top for bottom for some reason. */
263 glMatrixMode (GL_TEXTURE);
265 glMatrixMode (GL_MODELVIEW);
270 init_stars (ModeInfo *mi)
272 planetstruct *gp = &planets[MI_SCREEN(mi)];
274 int width = MI_WIDTH(mi);
275 int height = MI_HEIGHT(mi);
276 int size = (width > height ? width : height);
277 int nstars = size * size / 80;
280 int steps = max_size / inc;
283 if (MI_WIDTH(mi) > 2560) { /* Retina displays */
288 gp->starlist = glGenLists(1);
289 glNewList(gp->starlist, GL_COMPILE);
290 for (j = 1; j <= steps; j++)
292 glPointSize(inc * j * scale);
294 for (i = 0; i < nstars / steps; i++)
297 GLfloat r = 0.15 + frand(0.3);
298 GLfloat g = r + frand(d) - d;
299 GLfloat b = r + frand(d) - d;
301 GLfloat x = frand(1)-0.5;
302 GLfloat y = frand(1)-0.5;
303 GLfloat z = ((random() & 1)
305 : (BELLRAND(1)-0.5)/12); /* milky way */
306 d = sqrt (x*x + y*y + z*z);
311 glVertex3f (x, y, z);
318 check_gl_error("stars initialization");
322 #ifdef BLENDED_TERMINATOR
324 terminator_tube (ModeInfo *mi, int resolution)
326 Bool wire = MI_IS_WIREFRAME(mi);
328 GLfloat step = M_PI*2 / resolution;
329 GLfloat thickness = 0.1; /* Dusk is about an hour wide. */
330 GLfloat c1[] = { 0, 0, 0, 1 };
331 GLfloat c2[] = { 0, 0, 0, 0 };
340 glRotatef (90, 1, 0, 0);
341 glScalef (1.02, 1.02, 1.02);
342 glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
343 for (th = 0; th < M_PI*2 + step; th += step)
349 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c1);
350 glNormal3f (x, y, 0);
351 glVertex3f (x, y, thickness);
354 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c2);
355 glVertex3f (x, y, -thickness);
359 /* There's a bit of a spike in the shading where the tube overlaps
360 the sphere, so extend the sphere a lot to try and avoid that. */
361 # if 0 /* Nope, that doesn't help. */
364 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c1);
365 glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
366 for (th = 0; th < M_PI*2 + step; th += step)
370 glNormal3f (x, y, 0);
371 glVertex3f (x, y, thickness);
372 glVertex3f (x, y, thickness + 10);
378 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c2);
379 glBegin (wire ? GL_LINES : GL_QUAD_STRIP);
380 for (th = 0; th < M_PI*2 + step; th += step)
384 glNormal3f (x, y, 0);
385 glVertex3f (x, y, -thickness);
386 glVertex3f (x, y, -thickness - 10);
393 #endif /* BLENDED_TERMINATOR */
397 reshape_planet (ModeInfo *mi, int width, int height)
399 planetstruct *gp = &planets[MI_SCREEN(mi)];
400 GLfloat h = (GLfloat) height / (GLfloat) width;
402 glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
404 glViewport(0, 0, (GLint) width, (GLint) height);
405 glMatrixMode(GL_PROJECTION);
407 glFrustum(-1.0, 1.0, -h, h, 5.0, 200.0);
408 glMatrixMode(GL_MODELVIEW);
410 glTranslatef(0.0, 0.0, -40);
412 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
414 int o = (int) current_device_rotation();
415 if (o != 0 && o != 180 && o != -180)
420 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
425 planet_handle_event (ModeInfo *mi, XEvent *event)
427 planetstruct *gp = &planets[MI_SCREEN(mi)];
429 if (gltrackball_event_handler (event, gp->trackball,
430 MI_WIDTH (mi), MI_HEIGHT (mi),
439 init_planet (ModeInfo * mi)
442 int screen = MI_SCREEN(mi);
443 Bool wire = MI_IS_WIREFRAME(mi);
445 MI_INIT (mi, planets);
446 gp = &planets[screen];
448 gp->window = MI_WINDOW(mi);
450 if ((gp->glx_context = init_GL(mi)) != NULL) {
451 reshape_planet(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
455 char *f = get_string_resource(mi->dpy, "imageForeground", "Foreground");
456 char *b = get_string_resource(mi->dpy, "imageBackground", "Background");
458 if (!f) f = strdup("white");
459 if (!b) b = strdup("black");
461 for (s = f + strlen(f)-1; s > f; s--)
462 if (*s == ' ' || *s == '\t')
464 for (s = b + strlen(b)-1; s > b; s--)
465 if (*s == ' ' || *s == '\t')
473 double spin_speed = 0.1;
474 double wander_speed = 0.005;
475 gp->rot = make_rotator (do_roll ? spin_speed : 0,
476 do_roll ? spin_speed : 0,
478 do_wander ? wander_speed : 0,
481 gp->tilt = frand (23.4);
482 gp->trackball = gltrackball_init (True);
485 if (!wire && !do_texture)
487 GLfloat pos[4] = {1, 1, 1, 0};
488 GLfloat amb[4] = {0, 0, 0, 1};
489 GLfloat dif[4] = {1, 1, 1, 1};
490 GLfloat spc[4] = {0, 1, 1, 1};
491 glEnable(GL_LIGHTING);
493 glLightfv(GL_LIGHT0, GL_POSITION, pos);
494 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
495 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
496 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
508 /* construct the polygons of the planet
510 gp->platelist = glGenLists(1);
511 glNewList (gp->platelist, GL_COMPILE);
514 glRotatef (90, 1, 0, 0);
515 unit_sphere (resolution, resolution, wire);
519 gp->shadowlist = glGenLists(1);
520 glNewList (gp->shadowlist, GL_COMPILE);
524 glColor4f (0.5, 0.5, 0, 1);
525 # ifdef BLENDED_TERMINATOR
528 GLfloat c[] = { 0, 0, 0, 1 };
531 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
536 glScalef (1.01, 1.01, 1.01);
537 unit_dome (resolution, resolution, wire);
539 # ifdef BLENDED_TERMINATOR
540 terminator_tube (mi, resolution);
543 /* We have to draw the transparent side of the mask too,
544 though I'm not sure why. */
545 GLfloat c[] = { 0, 0, 0, 0 };
548 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
549 glRotatef (180, 1, 0, 0);
550 unit_dome (resolution, resolution, wire);
557 /* construct the polygons of the latitude/longitude/axis lines.
559 gp->latlonglist = glGenLists(1);
560 glNewList (gp->latlonglist, GL_COMPILE);
562 glRotatef (90, 1, 0, 0); /* unit_sphere is off by 90 */
563 glRotatef (8, 0, 1, 0); /* line up the time zones */
564 unit_sphere (12, 24, 1);
565 unit_sphere (12, 24, 1);
567 glVertex3f(0, -2, 0);
576 draw_planet (ModeInfo * mi)
578 planetstruct *gp = &planets[MI_SCREEN(mi)];
579 int wire = MI_IS_WIREFRAME(mi);
580 Display *dpy = MI_DISPLAY(mi);
581 Window window = MI_WINDOW(mi);
584 if (!gp->glx_context)
587 glDrawBuffer(GL_BACK);
588 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
590 glXMakeCurrent (dpy, window, *(gp->glx_context));
592 mi->polygon_count = 0;
594 if (gp->button_down_p)
596 else if (!gp->draw_axis && !(random() % 1000))
597 gp->draw_axis = 60 + (random() % 90);
599 if (do_rotate && !gp->button_down_p)
601 gp->z -= 0.001 * spin_arg; /* the sun sets in the west */
602 if (gp->z < 0) gp->z += 1;
605 glEnable(GL_LINE_SMOOTH);
606 glEnable(GL_POINT_SMOOTH);
607 glEnable(GL_DEPTH_TEST);
608 glEnable(GL_CULL_FACE);
613 get_position (gp->rot, &x, &y, &z, !gp->button_down_p);
617 glTranslatef(x, y, z);
619 gltrackball_rotate (gp->trackball);
623 get_rotation (gp->rot, &x, &y, 0, !gp->button_down_p);
624 glRotatef (x * 360, 1.0, 0.0, 0.0);
625 glRotatef (y * 360, 0.0, 1.0, 0.0);
628 glRotatef (current_device_rotation(), 0, 0, 1);
632 glDisable(GL_TEXTURE_2D);
634 glScalef (60, 60, 60);
635 glRotatef (90, 1, 0, 0);
636 glRotatef (35, 1, 0, 0);
637 glCallList (gp->starlist);
638 mi->polygon_count += gp->starcount;
640 glClear(GL_DEPTH_BUFFER_BIT);
643 glRotatef (90, 1, 0, 0);
644 glRotatef (35, 1, 0, 0);
645 glRotatef (10, 0, 1, 0);
646 glRotatef (120, 0, 0, 1);
655 glColor3f (0, 0, 0.5);
658 glColor4f (1, 1, 1, 1);
659 glEnable (GL_TEXTURE_2D);
660 glBindTexture (GL_TEXTURE_2D, gp->tex1);
664 GLfloat c[] = { 0, 0.5, 0, 1 };
666 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
670 glRotatef (gp->z * 360, 0, 0, 1);
671 glCallList (gp->platelist);
672 mi->polygon_count += resolution*resolution;
678 glRotatef (gp->tilt, 1, 0, 0);
681 glCallList (gp->shadowlist);
683 mi->polygon_count += resolution*(resolution/2);
687 else if (!do_texture || gp->tex2)
689 /* Originally we just used GL_LIGHT0 to produce the day/night sides of
690 the planet, but that always looked crappy, even with a vast number of
691 polygons, because the day/night terminator didn't exactly line up with
695 #ifndef BLENDED_TERMINATOR
697 /* Method 1, use the depth buffer as a stencil.
699 - Draw the full "day" sphere;
700 - Clear the depth buffer;
701 - Draw a rotated/tilted half-sphere into the depth buffer only,
702 on the Eastern hemisphere, putting non-zero depth only on the
704 - Draw the full "night" sphere, which will clip to dark parts only.
706 That lets us divide the sphere into the two maps, and the dividing
707 line can be at any angle, regardless of polygon layout.
709 The half-sphere is scaled slightly larger to avoid polygon fighting,
710 since those triangles won't exactly line up because of the rotation.
712 The downside of this is that the day/night terminator is 100% sharp.
714 glClear (GL_DEPTH_BUFFER_BIT);
715 glColorMask (0, 0, 0, 0);
716 glDisable (GL_TEXTURE_2D);
718 glRotatef (gp->tilt, 1, 0, 0);
719 glScalef (1.01, 1.01, 1.01);
720 glCallList (gp->shadowlist); /* Fill in depth on sunlit side */
721 mi->polygon_count += resolution*(resolution/2);
723 glColorMask (1, 1, 1, 1);
727 glEnable (GL_TEXTURE_2D);
728 glBindTexture (GL_TEXTURE_2D, gp->tex2);
732 GLfloat c[] = { 0, 0, 0.5, 1 };
735 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
739 glRotatef (gp->z * 360, 0, 0, 1);
740 glCallList (gp->platelist); /* Fill in color on night side */
741 mi->polygon_count += resolution*resolution;
744 #else /* BLENDED_TERMINATOR */
746 /* Method 2, use the alpha buffer as a stencil.
747 - Draw the full "day" sphere;
748 - Clear the depth buffer;
749 - Clear the alpha buffer;
750 - Draw a rotated/tilted half-sphere into the alpha buffer only,
751 on the Eastern hemisphere, putting non-zero alpha only on the
753 - Also draw a fuzzy terminator ring into the alpha buffer;
754 - Clear the depth buffer again;
755 - Draw the full "night" sphere, which will blend to dark parts only.
757 glColorMask (0, 0, 0, 1);
758 glClear (GL_COLOR_BUFFER_BIT);
759 glClear (GL_DEPTH_BUFFER_BIT);
760 glDisable (GL_TEXTURE_2D);
763 glRotatef (gp->tilt, 1, 0, 0);
764 glScalef (1.01, 1.01, 1.01);
765 glCallList (gp->shadowlist); /* Fill in alpha on sunlit side */
766 mi->polygon_count += resolution*(resolution/2);
769 glClear (GL_DEPTH_BUFFER_BIT);
771 glColorMask (1, 1, 1, 1);
773 GLfloat c[] = { 1, 1, 1, 1 };
776 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
779 glBlendFunc (GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
783 glEnable (GL_TEXTURE_2D);
784 glBindTexture (GL_TEXTURE_2D, gp->tex2);
788 GLfloat c[] = { 0, 0, 0.5, 1 };
790 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, c);
791 glEnable (GL_LIGHTING);
795 glRotatef (gp->z * 360, 0, 0, 1);
796 glCallList (gp->platelist); /* Fill in color on night side */
797 mi->polygon_count += resolution*resolution;
799 glDisable (GL_BLEND);
800 glBlendFunc (GL_ONE, GL_ZERO);
802 #endif /* BLENDED_TERMINATOR */
808 glRotatef (gp->z * 360, 0.0, 0.0, 1.0);
809 glScalef (1.02, 1.02, 1.02);
810 glDisable (GL_TEXTURE_2D);
811 glDisable (GL_LIGHTING);
812 glDisable (GL_LINE_SMOOTH);
813 glColor3f (0.1, 0.3, 0.1);
814 glCallList (gp->latlonglist);
815 mi->polygon_count += 24*24;
817 if (!wire && !do_texture)
818 glEnable (GL_LIGHTING);
819 if (gp->draw_axis) gp->draw_axis--;
823 if (mi->fps_p) do_fps (mi);
825 glXSwapBuffers(dpy, window);
830 free_planet (ModeInfo * mi)
832 planetstruct *gp = &planets[MI_SCREEN(mi)];
834 if (gp->glx_context) {
835 glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
837 if (glIsList(gp->platelist))
838 glDeleteLists(gp->platelist, 1);
839 if (glIsList(gp->starlist))
840 glDeleteLists(gp->starlist, 1);
845 XSCREENSAVER_MODULE_2 ("GLPlanet", glplanet, planet)