diff -c2 -P -r xscreensaver-4.06/driver/XScreenSaver.ad.in xscreensaver-4.06-glsperm/driver/XScreenSaver.ad.in *** xscreensaver-4.06/driver/XScreenSaver.ad.in 2002-10-23 20:34:46.000000000 -0500 --- xscreensaver-4.06-glsperm/driver/XScreenSaver.ad.in 2002-12-09 09:59:26.000000000 -0600 *************** *** 327,330 **** --- 327,331 ---- @GL_KLUDGE@ GL: flipscreen3d -root \n\ @GL_KLUDGE@ GL: glsnake -root \n\ + @GL_KLUDGE@ GL: glsperm -root -fpslo 25 \n\ @GL_KLUDGE@ GL: boxed -root \n\ @GL_KLUDGE@ GL: glforestfire -root \n\ *************** *** 621,624 **** --- 622,626 ---- *hacks.speedmine.name: SpeedMine *hacks.glsnake.name: GLSnake + *hacks.glsperm.name: GLSperm *hacks.glforestfire.name: GLForestFire *hacks.sballs.name: SBalls diff -c2 -P -r xscreensaver-4.06/driver/XScreenSaver_ad.h xscreensaver-4.06-glsperm/driver/XScreenSaver_ad.h *** xscreensaver-4.06/driver/XScreenSaver_ad.h 2002-10-23 22:26:18.000000000 -0500 --- xscreensaver-4.06-glsperm/driver/XScreenSaver_ad.h 2002-12-09 09:59:54.000000000 -0600 *************** *** 229,232 **** --- 229,233 ---- GL: flipscreen3d -root \\n\ GL: glsnake -root \\n\ + GL: glsperm -root -fpslo 25 \\n\ GL: boxed -root \\n\ GL: glforestfire -root \\n\ diff -c2 -P -r xscreensaver-4.06/hacks/config/glsperm.xml xscreensaver-4.06-glsperm/hacks/config/glsperm.xml *** xscreensaver-4.06/hacks/config/glsperm.xml 1969-12-31 18:00:00.000000000 -0600 --- xscreensaver-4.06-glsperm/hacks/config/glsperm.xml 2002-12-09 09:24:04.000000000 -0600 *************** *** 0 **** --- 1,64 ---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_description> + + An OpenGL world of little writhing tadpole creatures, + complete with illumination during the night part of + the cycle modeled after the creature lights of the + deep sea. + + The default Critter Count is 100. + + Setting the Frames/s Max to 0 unlimits it; anything + else sets an upper bound. + + Setting the Frames/s Min (Adaptive) to anything above + Off (range 1 to 100 in the GUI) selects that as a + target framerate to retain by dynamic world + modification, including overriding the Light Max + Index, Frames/s Max, and dynamically modifying the + critter count. + + The default Seconds in Day in 60. + The default Epoch basetime is 1970-01-01 00:00:00 GMT. + Use these together to set the day start-time and cycle length. + The Local Midnight option sets the Epoch to be the + most recent midnight in the local timezone. + + + diff -c2 -P -r xscreensaver-4.06/hacks/glx/Makefile.in xscreensaver-4.06-glsperm/hacks/glx/Makefile.in *** xscreensaver-4.06/hacks/glx/Makefile.in 2002-10-23 22:26:20.000000000 -0500 --- xscreensaver-4.06-glsperm/hacks/glx/Makefile.in 2002-12-09 08:15:38.000000000 -0600 *************** *** 87,91 **** cubenetic.c spheremonics.c marching.c lavalite.c rotator.c \ trackball.c gltrackball.c queens.c endgame.c chessmodels.c \ ! glblur.c OBJS = xscreensaver-gl-helper.o \ --- 87,91 ---- cubenetic.c spheremonics.c marching.c lavalite.c rotator.c \ trackball.c gltrackball.c queens.c endgame.c chessmodels.c \ ! glblur.c glsperm.c glsperm-random.c OBJS = xscreensaver-gl-helper.o \ *************** *** 106,110 **** cubenetic.o spheremonics.o marching.o lavalite.o rotator.o \ trackball.o gltrackball.o queens.o endgame.o chessmodels.o \ ! glblur.o GL_EXES = cage gears moebius pipes sproingies stairs superquadrics \ --- 106,110 ---- cubenetic.o spheremonics.o marching.o lavalite.o rotator.o \ trackball.o gltrackball.o queens.o endgame.o chessmodels.o \ ! glblur.o glsperm.o glsperm-random.o GL_EXES = cage gears moebius pipes sproingies stairs superquadrics \ *************** *** 113,117 **** dangerball circuit menger engine flipscreen3d glsnake boxed \ glforestfire sballs cubenetic spheremonics lavalite queens \ ! endgame glblur GLE_EXES = extrusion GL_UTIL_EXES = xscreensaver-gl-helper --- 113,118 ---- dangerball circuit menger engine flipscreen3d glsnake boxed \ glforestfire sballs cubenetic spheremonics lavalite queens \ ! endgame glblur glsperm ! GLE_EXES = extrusion GL_UTIL_EXES = xscreensaver-gl-helper *************** *** 130,134 **** stonerview-move.h stonerview-osc.h glutstroke.h \ glut_roman.h marching.h rotator.h trackball.h gltrackball.h \ ! chessmodels.h chessgames.h GL_MEN = atlantis.man boxed.man bubble3d.man cage.man circuit.man \ cubenetic.man dangerball.man engine.man extrusion.man \ --- 131,136 ---- stonerview-move.h stonerview-osc.h glutstroke.h \ glut_roman.h marching.h rotator.h trackball.h gltrackball.h \ ! chessmodels.h chessgames.h glsperm-random.h ! GL_MEN = atlantis.man boxed.man bubble3d.man cage.man circuit.man \ cubenetic.man dangerball.man engine.man extrusion.man \ *************** *** 139,143 **** spheremonics.man sproingies.man stairs.man starwars.man \ stonerview.man superquadrics.man xscreensaver-gl-helper.man \ ! endgame.man glblur.man MEN = @GL_MEN@ EXTRAS = README Makefile.in --- 141,146 ---- spheremonics.man sproingies.man stairs.man starwars.man \ stonerview.man superquadrics.man xscreensaver-gl-helper.man \ ! endgame.man glblur.man glsperm.man ! MEN = @GL_MEN@ EXTRAS = README Makefile.in *************** *** 496,499 **** --- 499,506 ---- $(CC_HACK) -o $@ $@.o tube.o $(HACK_OBJS) $(HACK_LIBS) + GLSPERM_OBJS=glsperm.o glsperm-random.o xpm-ximage.o + glsperm: $(GLSPERM_OBJS) + $(CC_HACK) -o $@ $(GLSPERM_OBJS) $(HACK_LIBS) $(XPM_LIBS) + ############################################################################## # *************** *** 569,572 **** --- 576,586 ---- glplanet.o: $(srcdir)/xpm-ximage.h glsnake.o: ../../config.h + glsperm.o: ../../config.h + glsperm.o: $(srcdir)/glsperm-random.h + glsperm.o: $(HACK_SRC)/images/glsperm-sky.xpm + glsperm.o: $(UTILS_SRC)/version.h + glsperm.o: $(UTILS_SRC)/vroot.h + glsperm.o: $(srcdir)/xpm-ximage.h + glsperm-random.o: $(srcdir)/glsperm-random.h gltext.o: ../../config.h gltext.o: $(srcdir)/gltrackball.h diff -c2 -P -r xscreensaver-4.06/hacks/glx/glsperm-random.c xscreensaver-4.06-glsperm/hacks/glx/glsperm-random.c *** xscreensaver-4.06/hacks/glx/glsperm-random.c 1969-12-31 18:00:00.000000000 -0600 --- xscreensaver-4.06-glsperm/hacks/glx/glsperm-random.c 2002-12-09 08:15:38.000000000 -0600 *************** *** 0 **** --- 1,162 ---- + /* random.c Copyright (c) 1992, 2002 Christopher Alexander North-Keys */ + /* incept 1992-02-01 */ + + #include + #include + #include "glsperm-random.h" + + #ifdef TEST + + #include + + static int all_tolerances_met = 1; + + int isAbout(float sample, float expected, float tolerance) + { + float tol = (tolerance > 0) ? tolerance : -tolerance; /* overkill */ + float lo = expected - tolerance; + float hi = expected + tolerance; + return ((lo <= sample) && (sample <= hi)); + } + + void outResult(const char *desc, float sample, float expected, float tolerance) + { + static const char *fmt = "%9.3f %9.3f %9.3f %9.3f [%s] %s\n"; + int tolerance_met = isAbout(sample, expected, tolerance); + all_tolerances_met = all_tolerances_met && tolerance_met; /* fail lasts */ + printf(fmt, sample, expected, sample - expected, tolerance, + (tolerance_met ? "pass" : "fail"), + desc); + } + + int main() + { + int max = 10; + int min = -max; /* don't change this, or an exact center will be lost */ + int trackers_raw[max - min + 1]; /* for -10 to 10, this yields 21 */ + int *trackers = &trackers_raw[max]; /* point to the exact center */ + + int count = 210000; /* generate this many */ + int sum = 0; /* track the sum, should be about zero */ + int sumpos = 0; /* track the sum of positives (and zeros)*/ + int sumneg = 0; /* track the sum of negatives (and zeros)*/ + int toopos = 0; /* track out-of-range positives */ + int tooneg = 0; /* track out-of-range negatives */ + int countpos = 0; + int countneg = 0; + int count0 = 0; + int i; + + for(i = min ; i <= max ; ++i) /* initial trackers */ + trackers[i] = 0; + + for(i = 1 ; i <= count ; ++i) + { + int number = Random(min, max); + sum += number; + if( number < min) ++tooneg; + else if(number > max) ++toopos; + else { + trackers[number]++; /* yes, that's with negative subscripts */ + if( number < 0 ) ++countneg, sumneg += number; + else if(number > 0 ) ++countpos, sumpos += number; + else ++count0, ++countneg, ++countpos; /* neg/pos includes 0s */ + } + } + + printf("numbers: %d from %d to %d\n", count, min, max); + puts(" sample expected deviation tolerance pass? description"); + { + float possible_values = (float)(max - min + 1); + float count_of_each_value_expected = (float)count / (max-min+1); + outResult("average", (float)sum / count, 0.0, 0.5); + outResult("average non-negatives (0 and up)", + (float)sumpos / countpos, (float)max/2, 0.25); + outResult("average non-positive (0 and down)", + (float)sumneg / countneg, (float)min/2, 0.25); + outResult("out-of-range positives", toopos, 0.0, 0.0); + outResult("out-of-range negatives", tooneg, 0.0, 0.0); + outResult("zeros", (float)count0, + count_of_each_value_expected, + count_of_each_value_expected / 30); + + for(i = min ; i <= max ; ++i) + { + char buf[128]; + sprintf(&buf[0], "value count for %d", i); + outResult(&buf[0], + (float)trackers[i], + count_of_each_value_expected, + count_of_each_value_expected / 30); + } + } + + for(i = 1 ; i < 100 ; ++i) + { + double number = RandomDouble(0, 10); + printf("%lf\n", number); + } + + printf("summary [%s], returning %d\n", + (all_tolerances_met ? "pass" : "fail"), + (all_tolerances_met ? 0 : -1)); + return (all_tolerances_met ? 0 : -1); + } + #endif /* TEST */ + + /* SVID 3 states rand() should now be a complete replacement for lrand48() */ + #if 0 + #define rand lrand48 + #define srand srand48 + #endif + + void RandomSeed(void) + { + time_t t; + time(&t); + srand((long)t); + return; + } + + int Random(int low, int high) + { + static int initialized = 0; + register int result = 0; + register int possibiities = -1; + + if( ! initialized) + RandomSeed(), ++initialized; + + if(low > high) + { + int tmp = low; + low = high; + high = tmp; + } + possibiities = high - low + 1; + + /* see Knuth's "Numerical Recipes in C" ch 7, or the Linux rand(3) manpage. + * "If you want to generate a random integer between 1 + * and 10, you should always do it by using high-order bits, as in + * j=1+(int) (10.0*rand()/(RAND_MAX+1.0)); + */ + + /* 0 to (possibiities - 1) */ + result = low + (int)( ((float)possibiities * rand()) / (RAND_MAX + 1.0) ); + + return result; + } + + double RandomDouble(double low, double high) + { + /* working with RAND_MAX directly causes trouble, so half is used here. */ + const unsigned long int Max = RAND_MAX / 2; + long double double_base = Random(0, Max); + long double range = high - low; + /* a rational number */ + /* |- between [0,1] -| */ + return low + ( (range * double_base) / Max ); + } + + + /* - - - - - - - - - - - - - - - - - - - - - - - eof - - - - - - - - - */ diff -c2 -P -r xscreensaver-4.06/hacks/glx/glsperm-random.h xscreensaver-4.06-glsperm/hacks/glx/glsperm-random.h *** xscreensaver-4.06/hacks/glx/glsperm-random.h 1969-12-31 18:00:00.000000000 -0600 --- xscreensaver-4.06-glsperm/hacks/glx/glsperm-random.h 2002-12-09 08:15:38.000000000 -0600 *************** *** 0 **** --- 1,11 ---- + /* random.h Copyright (c) 1992 Christopher Alexander North-Keys */ + /* random.h geboren Sat d.01.02.1992 */ + + #ifndef _seen_random_h + #define _seen_random_h + + extern int Random(int low, int high); + extern double RandomDouble(double low, double high); + + /* nu"r #endif herunten */ + #endif /* _seen_random_h */ diff -c2 -P -r xscreensaver-4.06/hacks/glx/glsperm.c xscreensaver-4.06-glsperm/hacks/glx/glsperm.c *** xscreensaver-4.06/hacks/glx/glsperm.c 1969-12-31 18:00:00.000000000 -0600 --- xscreensaver-4.06-glsperm/hacks/glx/glsperm.c 2002-12-09 08:15:38.000000000 -0600 *************** *** 0 **** --- 1,1901 ---- + /* glsperm.c : cute colorful "tadpoles" in a variably-lit world. + * Copyright (C) 2002 Christopher Alexander North-Keys + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Alternatively, the GNU General Public License can be found[1] at the + * GNU website at http://www.gnu.org/ + * + * The author of this program maintains[1] a website where contact info and + * other information may be found at http://www.talisman.org/~erlkonig/ + * Bear in mind that this software is UNSUPPORTED, and any replies or actions + * pursuant to requests are at the sole whim and discretion of the author. + * + * [1] At the time of this writing. + */ + + static const char *Version[] = { + ("$Copyright: " + "@(#) glsperm.c Copyright (C)2002 Christopher Alexander North-Keys, " + "under terms of the GNU General Public License, version 2 " + "$"), + "$Group: Talisman $", + "$Incept: 2002-09-12 10:13:01 GMT (Sep Thu) 1031825581 $", + "$Compiled: "__DATE__" " __TIME__ " $", + "$Source: /home/erlkonig/z/xscreensaver-mod/hacks/glx/glsperm.c $", + "$State: alpha $", + "$Revision: 0.9 $", + "$Date: 2002-09-16 02:28:49 CDT (Sep Mon) 1032161329 $", + "$Author: erlkonig $", + "$Website: http://www.talisman.org/~erlkonig/ $", + (const char*)0 + }; + + /* Translated from my original C++ program pursuant to joining xscreensaver */ + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Includes */ + #ifdef __cplusplus + extern "C" { + #endif + + #include + + #include + #include + #include + + #include + #include + #include + #include /* for memset(), etc. */ + #include + #include + #include + #include + #include + + #include + #include + #include "vroot.h" + #include "version.h" + + #include "glsperm-random.h" + #include "xpm-ximage.h" + #include "../images/glsperm-sky.xpm" + + #ifdef __cplusplus + }; + #endif + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Types */ + + #ifndef __cplusplus + typedef char bool; + enum { false = 0, true = 1 }; + #endif + + typedef struct _Port_t + { + char *display_string; /* don`t free this - src is getenv or argv */ + char *geometry; /* don`t free this - src is default or argv */ + Display *dpy; /* X display handle */ + Atom XA_WM_PROTOCOLS; + Atom XA_WM_DELETE_WINDOW; + Colormap colormap; + int screen; + int depth; + Visual *visual; /* no apparent need to free after use */ + bool running_on_root; /* default: false */ + bool running_in_prior_window; /* default: false */ + bool running_fullscreen; /* default: false */ + Window window; + Window root_window; + int root_w, root_h; /* width and height on root screen */ + int w, h, x, y; /* window size and position */ + bool geometry_has_xy_offsets; /* set by PortGeometryInterpret */ + GLXContext glx_context; + int lights_available; /* set in PortOpen() */ + bool is_nonrotating; /* toggle the viewpoint rotation */ + /* actually rendered, and may also affect */ + /* statistics */ /* critter count and other features. */ + unsigned long int frames_drawn; + double frame_first_t0; /* first frame start time of rendering */ + double frame_prior_t0; /* prior frame start time */ + double frame_prior_t1; /* prior frame time at render completion */ + double frame_this_t0; /* this frame`s render start time */ + double frame_this_t1; /* this frame`s render completion timestamp */ + } Port_t; + + typedef struct _Coord_t + { + float x, y, z; + } Coord_t; + + typedef struct _Segment_t + { + struct _Segment_t *prev, *next; + Coord_t position; + } Segment_t; + + typedef struct _Sperm_t + { + struct _Sperm_t *next; + Segment_t *segments; + Segment_t *segment_last; + unsigned int length; + unsigned int length_target; + float velocity_limit; + float displacement_limit; + float acceleration_limit; + float head_radius; + float tail_radius; + bool lit; + float bright_suppress; + Coord_t velocity; + } Sperm_t; + + typedef struct _World_t + { + Sperm_t *swarm; + unsigned int desired_count; + unsigned int population; + float sky_radius; + unsigned long int seconds_in_day; /* default 60 */ + unsigned long int timebase; /* default to unix epoch */ + float cycle_point; /* subpoint in cycle, range [0, 1) */ + float light_level; /* [light_bmin, light_max] */ + float light_min; /* [0, 1] constraint on light_level */ + float light_max; /* [0, 1] constraint on light_level */ + float night_bias; /* 0.0 = all daytime, 1.0 = all nighttime */ + unsigned int desired_lit_count; + bool is_aquatic; /* affects render, may add bubbles someday */ + bool is_strayer_hostile; /* kill sperm which are too far */ + bool is_sperm_variable; /* different-looking sperm? */ + bool is_environ_free; /* for speed purposes, disables sky */ + } World_t; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Globals */ + + /* NOTE: the X and OpenGL machines carry, of course, lots of additional + * global info. While the X is pretty much directed entirely via Port + * calls, be aware that quite a bit of work would be needed setting up the + * Ports for multiple OpenGL contexts before you`d be able to spawn two + * windows simultaneously, especially for the same World. :-) + */ + + char *progname = ""; + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Prototypes */ + + int Cout(const char *format, ...); + int Cerr(const char *format, ...); + void DefaultMaterial(void); + unsigned long int MicroAndSeconds(unsigned long int *seconds); + double TimeAsDouble(void); + double SleepDouble(double secs); /* return time actually slept */ + GLuint NewListID(void); /* this should probably be window-specific */ + int Sphere(int detail, bool inside_only_flag); /* radius 1 sphere. */ + void PortOutF(Port_t *p, FILE *out); + Port_t *PortNew(void); + bool PortOpen(Port_t *port, int argc, char **argv); + void PortDelete(Port_t **doomed_addr); /* shutdown Glx window and toss info */ + char PortEvents(Port_t *p); /* return keypress cmds as single bytes */ + bool PortGeometryInterpret(Port_t *p); + void PortViewpoint(Port_t *p); + void PortReshape(Port_t *port); + bool PortDefaults(Port_t *port); /* run initial GL commands */ + int PortRender(Port_t *p, World_t *w); + int PortRenderWorld(Port_t *p, World_t *w); + int PortRenderSegment(Port_t *p, World_t *w, Segment_t *s, float scale, int detail); + int PortRenderSky(Port_t *p, World_t *w); + bool PortRenderSpermLight(Port_t *p, World_t *w, Sperm_t *s, GLenum light); + int PortRenderSpermBulb(Port_t *p, World_t *w, Sperm_t *s); + int PortRenderSperm(Port_t *p, World_t *w, Sperm_t *s); + bool PortDepthFromVisual(Port_t *p); + float CoordAmplitude(Coord_t *c); + void CoordSetAmplitude(Coord_t *c, float amplitude_desired); + void CoordUnitRandom(Coord_t *c); /* convert to a random amplitude 1 vector */ + void CoordInvert(Coord_t *c); + void CoordPlusEquals(Coord_t *accum, const Coord_t *addthis); + void CoordMinusEquals(Coord_t *accum, const Coord_t *subthis); + unsigned long int LocalMidnight(void); + Segment_t *SegmentNew(Coord_t *pos); + Segment_t *SegmentExtract(Segment_t *extractee); /* return a list neighbor */ + void SegmentDeleteAll(Segment_t **doomed_addr); /* eradicates neighbors */ + void SegmentSetPosition(Segment_t *s, Coord_t *pos); + Sperm_t *SpermNew(bool is_variable); + void SpermDelete(Sperm_t **doomed_addr); + unsigned int SpermLength(Sperm_t *s); + void SpermLonger(Sperm_t *s); /* lengthen by one segment, the new head */ + void SpermShorter(Sperm_t *s); + void SpermUpdate(Sperm_t *s); + bool SpermLightDetails(Sperm_t *s, Coord_t *pos, Coord_t *dir); + void WorldOutF(World_t *w, FILE *out); + World_t *WorldNew(void); + void WorldDelete(World_t **doomed); + unsigned int WorldPopulation(World_t *w); + int WorldLightCount(World_t *w); + GLenum WorldLightForIndex(World_t *w, int index); + int WorldLitCount(World_t *w); + void WorldUpdate(World_t *w); + char *MakeTitleString(void); + void Usage(void); + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Functions */ + + int Cout(const char *format, ...) + { + int count = 0; + va_list parms; + va_start(parms, format); + count += vfprintf(stdout, format, parms); + va_end(parms); + return count; + } + + int Cerr(const char *format, ...) + { + int count = 0; + va_list parms; + va_start(parms, format); + count += fprintf(stderr, "%s", progname); + count += vfprintf(stderr, format, parms); + va_end(parms); + return count; + } + + unsigned long int MicroAndSeconds(unsigned long int *seconds) + { + struct timeval tv; + gettimeofday(&tv, (struct timezone*)0); + *seconds = tv.tv_sec; + return tv.tv_usec; + } + + double TimeAsDouble(void) + { + unsigned long int secs, microsecs; + microsecs = MicroAndSeconds(&secs); + return (double)secs + (double)microsecs / 1000000.0; + } + + double SleepDouble(double secs) /* return actual */ + { + double before = TimeAsDouble(), after; + struct timespec req; + req.tv_sec = (int)secs; /*1---___--- */ + req.tv_nsec = (secs - req.tv_sec) * 1000000000; + if((-1 == nanosleep(&req, &req)) && (errno == EINTR)) + nanosleep(&req, &req); /* give up after a possible 2nd try */ + after = TimeAsDouble(); + return after - before; + } + + unsigned long int LocalMidnight(void) + { + time_t now = time(0); + struct tm *tv = localtime(&now); + tv->tm_hour = tv->tm_min = tv->tm_sec = 0; /* set time to local midnight */ + return mktime(tv); + } + + void DefaultMaterial(void) + { + static bool initialized = false; + static GLfloat ambient[4]; + static GLfloat diffuse[4]; + static GLfloat specular[4]; + static GLfloat emission[4]; + static GLfloat shininess[1]; + + if( ! initialized) + { + glGetMaterialfv(GL_FRONT, GL_AMBIENT, ambient); + glGetMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse); + glGetMaterialfv(GL_FRONT, GL_SPECULAR, specular); + glGetMaterialfv(GL_FRONT, GL_EMISSION, emission); + glGetMaterialfv(GL_FRONT, GL_SHININESS, shininess); + initialized = true; + } + else + { + glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); + glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse); + glMaterialfv(GL_FRONT, GL_SPECULAR, specular); + glMaterialfv(GL_FRONT, GL_EMISSION, emission); + glMaterialfv(GL_FRONT, GL_SHININESS, shininess); + } + return; + } + + GLuint NewListID(void) /* this should probably be window-specific */ + { + static GLuint current = 0; + return ++current; /* first is 1 */ + } + + int Sphere(int detail, bool inside_only_flag) /* radius 1. return tri count */ + { + /* support complexity levels from 1 to 20 */ + enum { min = 1, max = 20 }; + static GLUquadricObj *sphere_inner[max + 1]; + static GLUquadricObj *sphere_outer[max + 1]; + static GLuint id_inner[max + 1]; + static GLuint id_outer[max + 1]; + static bool initialized = false; + if( ! (min <= detail) && (detail <= max)) { + static bool warned = false; + if( ! warned) { + Cerr(":%s: detail %d out of range [%d, %d]\n", + __FUNCTION__, detail, min, max); + warned = true; + } + return 0; + } + if( ! initialized) { + int i; + for(i = min ; i <= max ; ++i) + { + int detail = i; + /* inner glu */ + sphere_inner[i] = gluNewQuadric(); + gluQuadricOrientation(sphere_inner[i], GLU_INSIDE); + gluQuadricTexture(sphere_inner[i], GLU_TRUE); + /* outer glu */ + sphere_outer[i] = gluNewQuadric(); + gluQuadricTexture(sphere_outer[i], GLU_TRUE); + /* inner gl */ + id_inner[i] = NewListID(); + glNewList(id_inner[i], GL_COMPILE); + gluSphere(sphere_inner[i], 1, detail, detail); + glEndList(); + /* outer gl */ + id_outer[i] = NewListID(); + glNewList(id_outer[i], GL_COMPILE); + gluSphere(sphere_outer[i], 1, detail, detail); + glEndList(); + } + initialized = true; + } + glCallList(inside_only_flag + ? id_inner[detail] + : id_outer[detail]); + return detail * detail * 2; + } + + int PortRenderSky(Port_t *p, World_t *w) /* 0 = dawn, 0.25 = noon... */ + { + int faces = 0; + GLfloat au = 0.90; /* distance to sun from world (A.U.), sky is 1.0 */ + GLfloat lite = w->light_level; + GLfloat lite_sky = lite * 0.8 + 0.2; /* dark gray to white + sky tex */ + float cycle = w->cycle_point; + GLfloat sunset = (sin((cycle - 0.25) * 2 * M_PI) + 1.0) / 2.0;/*peak @.25*/ + + /* Sun mode */ + GLfloat sunny = pow((double)lite, (double)0.58); /* lower for more light */ + GLfloat lite_ambS = sunny * 0.5; + GLfloat lite_difS = sunny * 1.5; + GLfloat lightambS[4] = { lite_ambS, lite_ambS, lite_ambS, 1.0 }; + GLfloat lightdifS[4] = { lite_difS, lite_difS, lite_difS + 0.1, 1.0 }; + GLfloat lightspeS[4] = { lite_difS, lite_difS, lite_difS + 0.1, 1.0 }; + GLfloat lightposS[4] = { cos(cycle * 2*M_PI) * au, /* right 0 left 0 */ + sin(cycle * 2*M_PI) * au, /* 0 up 0 down */ + sin(cycle * 2*M_PI) * au, /* 0 front 0 back */ + 1.0 }; /* positional */ + + glLightfv(GL_LIGHT0, GL_AMBIENT, lightambS); + glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdifS); + glLightfv(GL_LIGHT0, GL_SPECULAR, lightspeS); + glLightfv(GL_LIGHT0, GL_POSITION, lightposS); + glLightf (GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0); + glLightf (GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0); + glLightf (GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0); + + glPushMatrix(); + glRotatef(90, 1, 0, 0); /* hide less-well-textured sphere areas. */ + + glEnable(GL_LIGHT0); + /* glDisable(GL_FOG); */ + + if(w->is_environ_free) + { + glColor3f(0.0 + 0.5*sunset, + 0.0 + 0.1*sunset, + 0.8 - 0.2*sunset); + Sphere(12, true); /* args: 'detail, 'inner-only-flag */ + } else { + static bool initialized = false; + if( ! initialized) + { + XImage *image = xpm_to_ximage(p->dpy, p->visual, p->colormap, + glsperm_sky); + if(image) + { + while(glGetError() != GL_NO_ERROR); + glTexImage2D(GL_TEXTURE_2D, + 0, GL_RGBA, image->width, image->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, image->data); + if(glGetError() == GL_NO_ERROR) + initialized = true; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); + glTexGeni (GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); + } + glColor3f(lite_sky + 0.5*sunset, + lite_sky + 0.1*sunset, + lite_sky - 0.2*sunset); + glEnable(GL_TEXTURE_2D); + DefaultMaterial(); + faces += Sphere(12, true); /* args: 'detail, 'inner-only-flag */ + glDisable(GL_TEXTURE_2D); + } + + /* glEnable(GL_FOG);*/ + glPopMatrix(); + + /* glDisable(GL_LIGHT0); */ + return faces; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - Port */ + + void PortOutF(Port_t *p, FILE *out) + { + fprintf(out, "Port %#lx\n", (unsigned long int)p); + fprintf(out, " display: \"%s\" -> %#lx\n", + p->display_string, (unsigned long int)(p->dpy)); + fprintf(out, " screen %d, depth %d, visual id %ld\n", + p->screen, p->depth, p->visual ? p->visual->visualid : -1); + fprintf(out, " colormap %ld\n", p->colormap); + fprintf(out, " root window %ld, window in use %ld, at =%dx%d+%d+%d\n", + p->root_window, p->window, + p->w, p->h, p->x, p->y); + fprintf(out, " on root? %s, in prior window? %s, fullscreen? %s\n", + p->running_on_root ? "yes" : "no", + p->running_in_prior_window ? "yes" : "no", + p->running_fullscreen ? "yes" : "no"); + fprintf(out, " root size %dx%d\n", p->root_w, p->root_h); + fprintf(out, " geometry %dx%d+%d+%d\n", + p->w, p->h, p->x, p->y); + fprintf(out, " GLX context %#lx\n", (unsigned long int)p->glx_context); + fprintf(out, " lights available %d\n", p->lights_available); + } + + Port_t *PortNew() + { + Port_t *hi = (Port_t*)calloc(1, sizeof(*hi)); + if(hi) + { + /* default values */ + hi->display_string = getenv("DISPLAY"); + hi->geometry = "=400x300"; + } else { + Cerr(":%s:calloc failed - %s\n", __FUNCTION__, strerror(errno)); + return hi; + } + return hi; + } + + /* create and setup a Glx window */ + bool PortOpen(Port_t *port, int argc, char **argv) + { + /* note that this function doesn't make any gl* calls */ + Window rootwindow; + + if( ! port) + return Cerr(":%s: error - port param null\n", __FUNCTION__), false; + + if( ! (port->dpy = XOpenDisplay(port->display_string))) + return Cerr(":%s:XOpenDisplay failed\n", __FUNCTION__), false; + + port->w = port->h = 500; /* '' */ + port->screen = DefaultScreen(port->dpy); + port->root_window = RootWindow(port->dpy, port->screen); + + port->XA_WM_PROTOCOLS = XInternAtom(port->dpy,"WM_PROTOCOLS", False); + port->XA_WM_DELETE_WINDOW= XInternAtom(port->dpy,"WM_DELETE_WINDOW",False); + + rootwindow = RootWindow(port->dpy, port->screen); + + /* arrange for port->window to override and set port->running_on_root */ + if(port->window && port->running_on_root) + if(port->window != rootwindow) + port->running_on_root = false; + + if(port->running_on_root) + { + port->window = rootwindow; + /* the port->fullscreen action isn`t necessary. */ + } + + if(port->window) + { + if(port->window != rootwindow) + port->running_on_root = false; + XWindowAttributes xgwa; + XGetWindowAttributes(port->dpy, port->window, &xgwa); + port->visual = xgwa.visual; + port->w = xgwa.width; + port->h = xgwa.height; + } else { + XSizeHints hints; memset(&hints, 0, sizeof(hints)); + port->root_w = WidthOfScreen(DefaultScreenOfDisplay(port->dpy)); + port->root_h = HeightOfScreen(DefaultScreenOfDisplay(port->dpy)); + + if(port->running_fullscreen) + { + port->w = port->root_w; + port->h = port->root_h; + } + else if(PortGeometryInterpret(port)) + { + hints.flags = USSize; + hints.width = port->w; + hints.height = port->h; + if(port->geometry_has_xy_offsets) + { + hints.flags |= USPosition; + hints.x = port->x; + hints.y = port->y; + } + } + + { /* Pick a good GL visual */ + enum { RS = GLX_RED_SIZE, + GS = GLX_GREEN_SIZE, + BS = GLX_BLUE_SIZE, + DS = GLX_DEPTH_SIZE, + IS = GLX_BUFFER_SIZE, + DB = GLX_DOUBLEBUFFER }; + + int attrs[][20] = { + { GLX_RGBA, RS, 8, GS, 8, BS, 8, DS, 8, DB, 0 },/*rgb double*/ + { GLX_RGBA, RS, 4, GS, 4, BS, 4, DS, 4, DB, 0 }, + { GLX_RGBA, RS, 2, GS, 2, BS, 2, DS, 2, DB, 0 }, + { GLX_RGBA, RS, 8, GS, 8, BS, 8, DS, 8, 0 },/*rgb single*/ + { GLX_RGBA, RS, 4, GS, 4, BS, 4, DS, 4, 0 }, + { GLX_RGBA, RS, 2, GS, 2, BS, 2, DS, 2, 0 }, + { IS, 8, DS, 8, DB, 0 },/*cmap double*/ + { IS, 4, DS, 4, DB, 0 }, + { IS, 8, DS, 8, 0 },/*cmap single*/ + { IS, 4, DS, 4, 0 }, + { GLX_RGBA, RS, 1, GS, 1, BS, 1, DS, 1, 0 } /*monochrome*/ + }; + + unsigned int i; + for(i = 0 ; i < (sizeof attrs / sizeof *attrs); ++i) + { + XVisualInfo *vi = glXChooseVisual(port->dpy, port->screen, + attrs[i]); + if(vi) + { + port->visual = vi->visual; + XFree(vi); + break; + } + } + } + + if( ! port->visual) + return Cerr(":%s: GL visual unavailable.\n", __FUNCTION__), false; + + { + XSetWindowAttributes xswa; + unsigned long xswa_mask = ( CWEventMask | CWColormap + | CWBackPixel | CWBackingPixel + | CWBorderPixel ); + xswa.colormap = XCreateColormap(port->dpy, + port->root_window, + port->visual, AllocNone); + xswa.background_pixel = BlackPixel(port->dpy, port->screen); + xswa.backing_pixel = xswa.background_pixel; + xswa.border_pixel = xswa.background_pixel; + xswa.event_mask = ( KeyPressMask + | ButtonPressMask + | StructureNotifyMask); + + PortDepthFromVisual(port); + port->window = XCreateWindow(port->dpy, port->root_window, + port->x, port->y, port->w, port->h, + 0, port->depth, + InputOutput, port->visual, + xswa_mask, &xswa); + } + + { + XWMHints wmhints; + XTextProperty tp1, tp2; + char *title = MakeTitleString(); /* check fun: may need freeing */ + XStringListToTextProperty(&title, 1, &tp1); + XStringListToTextProperty(&progname, 1, &tp2); + wmhints.flags = InputHint; + wmhints.input = True; + + XSetWMProperties(port->dpy, port->window, &tp1, &tp2, + argv, argc, &hints, &wmhints, 0); + } + + XChangeProperty (port->dpy, port->window, + port->XA_WM_PROTOCOLS, XA_ATOM, 32, + PropModeReplace, + (unsigned char *)&(port->XA_WM_DELETE_WINDOW), 1); + + XMapRaised (port->dpy, port->window); + XSync (port->dpy, False); + } + + /* Now hook up to GLX */ + { + XVisualInfo vi_in, *vi_out; + int out_count; + vi_in.screen = port->screen; + vi_in.visualid = XVisualIDFromVisual(port->visual); + vi_out = XGetVisualInfo(port->dpy, + VisualScreenMask | VisualIDMask, + &vi_in, &out_count); + if(vi_out) + { + port->glx_context = glXCreateContext(port->dpy, vi_out, 0,GL_TRUE); + XFree((char*)vi_out); + + if(port->glx_context) + { + glXMakeCurrent(port->dpy, port->window, port->glx_context); + /* SUCCESS ! */ + } else Cerr(": couldn't create GL context for root window.\n"); + } else Cerr(": couldn't get visual info.\n"); + } + + glGetIntegerv(GL_MAX_LIGHTS, &(port->lights_available)); + --(port->lights_available); /* decrement to account for LIGHT0 always on */ + PortReshape(port); + PortDefaults(port); + + return true; + } + + void PortDelete(Port_t **doomed_addr) /* shutdown Glx window and toss info */ + { + if(doomed_addr) + { + Port_t *doomed = *doomed_addr; + if(doomed) /* the following is mostly raving compulsiveness :-) */ + { + if(doomed->glx_context) + glXDestroyContext(doomed->dpy, doomed->glx_context); + if(doomed->window && ! ( doomed->running_in_prior_window + || doomed->running_on_root)) + XDestroyWindow(doomed->dpy, doomed->window); + if(doomed->colormap) + XFreeColormap(doomed->dpy, doomed->colormap); + if(doomed->dpy) + XCloseDisplay(doomed->dpy); + free(doomed); + } + *doomed_addr = 0; + } + } + + bool PortGeometryInterpret(Port_t *p) /* format: [[=]NxN]][±N±N] */ + { + bool succp = false; /* skip leading `=` if present */ + char *geometry = ('=' == p->geometry[0]) ? (p->geometry + 1) : p->geometry; + if( ! geometry) /* sanity check */ + return succp; /* still false of course */ + /* insure valid chars and nothing line =100x100--4+-4 */ + if( strspn(geometry, "0123456789=+-x") == strlen(geometry) + && ! ( strstr(geometry, "--") || strstr(geometry, "++") + || strstr(geometry, "+-") || strstr(geometry, "-+"))) + { + int w = -1, h = -1, x = -1, y = -1; /* invalids */ + /* delimiters we expect: */ + char d_wh = '\0'; /* the `x` between width and height */ + char d_xo = '\0'; /* the `±` before the x offset */ + char d_yo = '\0'; /* the `±` before the y offset */ + char oops = '\0'; /* this one should -not- end up being read. */ + int fields = (isdigit(geometry[0]) + ? sscanf(geometry, "%d%c%d%c%d%c%d%c", + &w, &d_wh, &h, &d_xo, &x, &d_yo, &y, &oops) + : sscanf(geometry, "%c%d%c%d%c", + &d_xo, &x, &d_yo, &y, &oops)); + if((3 == fields) || (7 == fields)) /* may have W and H */ + if((succp = ((w > 0) && ('x' == d_wh) && (h > 0)))) + p->w = w, p->h = h; + if((4 == fields) || (succp && (7 == fields))) /* may have X and Y */ + if((succp = ( strchr("+-", d_xo) && (x >= 0) + && strchr("+-", d_yo) && (y >= 0)))) + { + p->x = (('-' == d_xo) ? (p->root_w - p->w - x) : x); + p->y = (('-' == d_yo) ? (p->root_h - p->h - y) : y); + p->geometry_has_xy_offsets = true; + } + if( ! succp) Cerr(":%s: bad field count in geometry\n", __FUNCTION__); + } else Cerr(":%s: invalid characters in geometry\n", __FUNCTION__); + return succp; + } + + bool PortDepthFromVisual(Port_t *p) /* prereqs: p->{dpy,screen,visual} */ + { + bool succp = false; + XVisualInfo vi_in, *vi_out = 0; + int out_count = 0; + vi_in.screen = p->screen; + vi_in.visualid = XVisualIDFromVisual(p->visual); + vi_out = XGetVisualInfo(p->dpy, VisualScreenMask | VisualIDMask, + &vi_in, &out_count); + if(vi_out) + { + p->depth = vi_out[0].depth; + succp = true; + XFree((char*)vi_out); + } + return succp; + } + + char PortEvents(Port_t *p) /* return keypress cmds as single bytes */ + { + while(XPending(p->dpy)) + { + XEvent ev; + XNextEvent(p->dpy, &ev); + switch(ev.xany.type) + { + case ConfigureNotify: + { + XWindowAttributes xgwa; + XGetWindowAttributes(p->dpy, p->window, &xgwa); + p->w = xgwa.width, p->h = xgwa.height; + PortReshape(p); + break; + } + case KeyPress: + { + KeySym keysym; + char c = 0; + if(0 < XLookupString(&ev.xkey, &c, 1, &keysym, 0)) /*get any?*/ + /* note that (ev.xkey.x, ev.xkey.y) are also available */ + /* Preėmptive Return */ + return c; /* return for next events later */ + } + break; + case ButtonPress: + XBell(p->dpy, 0); + break; + case ClientMessage: + if(p->XA_WM_PROTOCOLS != ev.xclient.message_type) + { + char *s = XGetAtomName(p->dpy, ev.xclient.message_type); + if( ! s) s = "(null)"; + Cerr(":%s: unknown ClientMessage \"%s\" recvd\n", __FUNCTION__, + s); + } + else if(p->XA_WM_DELETE_WINDOW != (unsigned long)ev.xclient.data.l[0]) + { + char *s1 = XGetAtomName(p->dpy, ev.xclient.message_type); + char *s2 = XGetAtomName(p->dpy, ev.xclient.data.l[0]); + if( ! s1) s1 = "(null)"; + if( ! s2) s2 = "(null)"; + Cerr(":%s: unknown ClientMessage \"%s\" [\"%s\"] received\n", + __FUNCTION__, + s1, s2); + } + break; + } + } + return '\0'; + } + + void PortViewpoint(Port_t *p) + { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(30.0, 1280.0/1024.0, 0.1, 50); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(0, 0, 30, /* eye pos */ + 0, 0, 0, /* reference point */ + 0, 1, 0); /* up */ + { + static GLfloat rotation = 0; + if( ! p->is_nonrotating) + rotation += 0.5; + glRotatef(rotation, 0, 1, 0); + } + } + void PortReshape(Port_t *port) + { + glMatrixMode(GL_MODELVIEW); + glViewport(0, 0, port->w, port->h); + glLoadIdentity(); + } + + bool PortDefaults(Port_t *port) /* run initial GL commands */ + { + glShadeModel(GL_SMOOTH); + glEnable(GL_COLOR_MATERIAL); + + glEnable(GL_CULL_FACE); glCullFace(GL_BACK); + glEnable(GL_NORMALIZE); + + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); + + { + GLfloat amb[4] = { 0.0, 0.0, 0.0, 1.0 }; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb); + } + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); + glEnable(GL_LIGHTING); + + glColor3f(1.0, 1.0, 1.0); + glClearColor(0.0, 0.0, 0.0, 1.0); + + #if 0 + { + int light_count = -1; + glGetIntegerv(GL_MAX_LIGHTS, &light_count); + Cout("[actual light max is %d]\n", light_count); + } + #endif + + return true; + } + + /* this is lighting setup only - the bright aura is in PortRenderSpermBulb */ + bool PortRenderSpermLight(Port_t *p, World_t *w, Sperm_t *s, GLenum light) + { + bool succp = false; + if(s && s->lit && (w->light_level < s->bright_suppress) + && (GL_NONE != light)) + { + Coord_t bulb, dir; /* bulb position and beam (cone) direction */ + if(SpermLightDetails(s, &bulb, &dir)) + { + GLfloat lightpos[4] = { bulb.x, bulb.y, bulb.z, 1 }; + GLfloat lightamb[4] = { 0.0, 0.0, 0.0, 1.0 }; + GLfloat lightdif[4] = { 0.8, 0.8, 0.7, 1.0 }; + GLfloat lightspe[4] = { 0.8, 0.8, 0.4, 1.0 }; + /* GLfloat lightdir[3] = { dir.x, dir.y, dir.z}; */ + glLightfv(light, GL_POSITION, lightpos); + glLightfv(light, GL_AMBIENT, lightamb); + glLightfv(light, GL_DIFFUSE, lightdif); + glLightfv(light, GL_SPECULAR, lightspe); + glLightf (light, GL_CONSTANT_ATTENUATION, 0.00); + glLightf (light, GL_LINEAR_ATTENUATION, 0.00); + glLightf (light, GL_QUADRATIC_ATTENUATION, + w->is_aquatic ? 0.25 : 0.12); + glEnable (light); + succp = true; + } + } else { + glDisable(light); + } + return succp; + } + + int PortRenderSpermBulb(Port_t *p, World_t *w, Sperm_t *s) + { + int faces = 0; + if(s->lit) /* render the light and glowing aura around */ + { + Segment_t bulb; /* position needs to be set */ + Coord_t bulbdir; + GLboolean was_lit; + /**/ glPushMatrix(); + glGetBooleanv(GL_LIGHTING, &was_lit); + SpermLightDetails(s, &bulb.position, &bulbdir); + + if(w->light_level < s->bright_suppress) + { + if(was_lit) glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE); + glEnable(GL_BLEND); + glDepthMask(GL_FALSE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + /* the light */ + { + float scale0 = 0.25; + float rgb = 0.4; /* default if suppressed */ + /* the aura shells */ + if(w->light_level < s->bright_suppress) + { + float scalemax = 12 - (12 * w->light_level); + float scale; + float alpha = w->is_aquatic ? 0.020 : 0.015; + /* float alpha_adjust = pow((1 - w->light_level), 3.0); */ + /* alpha *= alpha_adjust; */ + alpha += (double)(1 - w->light_level) / 1000.0; + /* Cerr("%f\n", alpha_adjust); + */ + for(scale = scale0 ; scale <= scalemax ; scale *= 1.1) + { + if(w->is_aquatic) + glColor4f(0.7, 0.8, 1.0, alpha); + else + glColor4f(1, 1, 1, alpha); + faces += PortRenderSegment(p, w, &bulb, + s->head_radius * scale, 9); + } + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glEnable(GL_TEXTURE); + if(was_lit) glEnable(GL_LIGHTING); + rgb = 1.0; + } + /* the bulb */ + { + static GLfloat bulb_glow[4] = { 1, 1, 1, 1 }; + static GLfloat no_glow[4] = { 0, 0, 0, 1 }; + if(w->light_level < s->bright_suppress) + glMaterialfv(GL_FRONT, GL_EMISSION, &bulb_glow[0]); + glColor4f(rgb, rgb, rgb, 1.00); + faces += PortRenderSegment(p, w, &bulb, + s->head_radius * scale0, 0); + if(w->light_level < s->bright_suppress) + glMaterialfv(GL_FRONT, GL_EMISSION, &no_glow[0]); + } + } + /* the post */ + { + Coord_t *bulbpos = &bulb.position; + Coord_t *headpos = &(s->segments->position); + glBegin(GL_LINES); + glColor3f(sqrt(fabs(headpos->x) / s->displacement_limit), + sqrt(fabs(headpos->y) / s->displacement_limit), + sqrt(fabs(headpos->z) / s->displacement_limit)); + glVertex3f(headpos->x, headpos->y, headpos->z); + glColor3f(1, 1, 1); + glVertex3f(bulbpos->x, bulbpos->y, bulbpos->z); + glEnd(); + faces += 1; + } + /**/ glPopMatrix(); + } + return faces; + } + + int PortRenderSperm(Port_t *p, World_t *w, Sperm_t *s) + { + int faces = 0; + Segment_t *seg = 0; + Segment_t *prior = 0; + glPushMatrix(); + faces += PortRenderSpermBulb(p, w, s); + { + double fract = 1.0; + double fract_final = 0.4; /* sz of tail end as ratio vs tail begin */ + double fract_delta = pow(fract_final, (1/(double)s->length_target)); + + for(seg = s->segments ; seg ; prior = seg, seg = seg->next) + { + Coord_t *here = &(seg->position); + + glColor3f(sqrt(fabs(here->x) / s->displacement_limit), + sqrt(fabs(here->y) / s->displacement_limit), + sqrt(fabs(here->z) / s->displacement_limit)); + if(seg == s->segments) /* first one, the head */ + { + /* set the texture for the head, when implemented */ + faces += PortRenderSegment(p, w, seg, s->head_radius, 10); + /* set the texture for the tail, when implemented */ + } else { + #if 0 + glBegin(GL_LINES); + glVertex3f(prior->x, prior->y, prior->z), + glVertex3f(here.x, here.y, here.z), + glEnd(); + #endif + faces += PortRenderSegment(p, w, seg, + s->tail_radius * fract, 4); + } + fract *= fract_delta; + } + } + glPopMatrix(); + return faces; + } + + int PortRenderSegment(Port_t *p, World_t *w, Segment_t *s, float scale, int detail) + { + int faces = 0; + if( ! detail) detail = 6; + glPushMatrix(); + glTranslatef(s->position.x, s->position.y, s->position.z); + glScalef(scale, scale, scale); + faces += Sphere(detail, 0); /* no 2nd arg defaults to outer faces */ + glPopMatrix(); + return faces; + } + + int PortRenderWorld(Port_t *p, World_t *w) + { /* 0 = dawn, 0.25 = noon...*/ + int faces = 0; + glPushMatrix(); + glScalef(w->sky_radius, w->sky_radius, w->sky_radius); + faces += PortRenderSky(p, w); + glPopMatrix(); + + { + GLfloat gold[] = { 1, 1, 0, 1 }; + GLfloat cyan[] = { 0, 1, 1, 1 }; + GLfloat mat_shininess[] = { 60.0 }; + glMaterialfv(GL_FRONT, GL_SPECULAR, gold); + glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); + glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cyan); + } + + if(w->population > 0) + { + int lights_used = -1; + Sperm_t *s = 0; + for(s = w->swarm ; s ; s = s->next) + { + /* was a light-carrying, un-light-suppressed critter */ + /* successful in getting an OpenGL light (of the set) to use? */ + if(PortRenderSpermLight(p, w, s, + WorldLightForIndex(w, lights_used+1))) + { + ++lights_used; + if(lights_used >= WorldLightCount(w)) + { + static bool warned = false; + if( ! warned) { + Cerr(":%s: too many lights used (%d) max is %d\n", + __FUNCTION__, + lights_used, WorldLightCount(w)); + warned = true; + } + break; + } + } + } + + { /* now render the whole set normally */ + int light; + /* static Texture tex("texture/ice.rgb"); */ + /* tex.enable(GL_MODULATE); */ + for(s = w->swarm ; s ; s = s->next) + faces += PortRenderSperm(p, w, s); + /* tex.disable(); */ + /* and disable whichever lights we used */ + for(light = 1 ; light <= lights_used ; ++light) + glDisable(WorldLightForIndex(w, light)); + } + + } + return faces; + } + + int PortRender(Port_t *p, World_t *w) + { + int faces = 0; + + { /* statistics: pre-render stage */ + p->frame_prior_t0 = p->frame_this_t0; /* old "this" info to "prior" */ + p->frame_prior_t1 = p->frame_this_t1; /* old "this" info to "prior" */ + p->frame_this_t0 = TimeAsDouble(); + if( ! p->frame_first_t0) + p->frame_first_t0 = p->frame_this_t0; /* potential div-by-0 risk */ + } + + glDrawBuffer(GL_BACK); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + PortViewpoint(p); + + DefaultMaterial(); + + glPushMatrix(); + + if(w->is_aquatic) + { + GLfloat fog[4] = { 0.1 * w->light_level, + 0.1 * w->light_level, + 0.3 * w->light_level, 1.0 }; + glEnable(GL_FOG); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_START, 1.0); + glFogf(GL_FOG_END, 30 + (0.9 * w->sky_radius)); /* 30: PortViewpoint */ + glFogfv(GL_FOG_COLOR, fog); + } else { + glDisable(GL_FOG); + } + + faces += PortRenderWorld(p, w); + /* printf("faces: %d\n", faces); + */ + glPopMatrix(); + + glFlush(); + glXSwapBuffers(p->dpy, p->window); + + { /* statistics: post-render stage */ + ++(p->frames_drawn); + p->frame_this_t1 = TimeAsDouble(); + } + + return faces; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - Coord */ + + float CoordAmplitude(Coord_t *c) + { + return sqrt( c->x * c->x + + c->y * c->y + + c->z * c->z); + } + + void CoordSetAmplitude(Coord_t *c, float amplitude_desired) + { + float len = CoordAmplitude(c); + if(len > 0) { + float scale = amplitude_desired / len; + c->x *= scale; + c->y *= scale; + c->z *= scale; + } else { + c->x = 1; + c->y = 0; + c->z = 0; + } + } + + void CoordUnitRandom(Coord_t *c) /* convert to a random amplitude 1 vector */ + { + /* r^2 = x^2 + y^2 + z^2 = 1 -- looks linear for the squares */ + double xsq = RandomDouble(0.1, 1.0); /* avoid zeros */ + double ysq = RandomDouble(0.1, 1.0); + double zsq = RandomDouble(0.1, 1.0); + double amplitude = sqrt(xsq + ysq + zsq); /* why we avoided zeros */ + c->x = sqrt(xsq) * (Random(0,1) ? 1 : -1); + c->y = sqrt(ysq) * (Random(0,1) ? 1 : -1); + c->z = sqrt(zsq) * (Random(0,1) ? 1 : -1); + c->x /= amplitude; + c->y /= amplitude; + c->z /= amplitude; + } + + void CoordInvert(Coord_t *c) + { + c->x *= -1; + c->y *= -1; + c->z *= -1; + } + + void CoordPlusEquals(Coord_t *accum, const Coord_t *addthis) + { + if(accum && addthis) + { + accum->x += addthis->x; + accum->y += addthis->y; + accum->z += addthis->z; + } else + Cerr(":%s: error - args of (%#lx, %#lx) not both valid\n", + __FUNCTION__, + accum, addthis); + } + + void CoordMinusEquals(Coord_t *accum, const Coord_t *subthis) + { + if(accum && subthis) + { + accum->x -= subthis->x; + accum->y -= subthis->y; + accum->z -= subthis->z; + } else + Cerr(":%s: error - args of (%#lx, %#lx) not both valid\n", + __FUNCTION__, + accum, subthis); + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - Segment */ + + Segment_t *SegmentNew(Coord_t *pos) + { + Segment_t *hi = (Segment_t*)calloc(1, sizeof *hi); + if(hi) + { + hi->position = *pos; + } else Cerr(":%s: calloc failed - %s\n", __FUNCTION__, strerror(errno)); + return hi; + } + + Segment_t *SegmentExtract(Segment_t *extractee) /* return a list neighbor */ + { + Segment_t *prev = extractee->prev; + Segment_t *next = extractee->next; + if(prev) prev->next = next; + if(next) next->prev = prev; + extractee->prev = extractee->next = 0; + return next ? next : prev; + } + + void SegmentDeleteAll(Segment_t **doomed_addr) /* eradicates neighbors too */ + { + if(doomed_addr && *doomed_addr) + { + Segment_t *doomed = *doomed_addr; + Segment_t *prev = doomed->prev; + Segment_t *next = doomed->next; + /* disconnect the 2 sublists and recurse */ + if(prev) + prev->next = 0, SegmentDeleteAll(&prev); + if(next) + next->prev = 0, SegmentDeleteAll(&next); + free(doomed); + *doomed_addr = 0; + } else Cerr(":: called with null parm or parm target\n", __FUNCTION__); + } + + void SegmentSetPosition(Segment_t *s, Coord_t *pos) + { + s->position = *pos; /* struct assign */ + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - Sperm */ + + Sperm_t *SpermNew(bool is_variable) + { + Sperm_t *hi = (Sperm_t*)calloc(1, sizeof *hi); + if(hi) + { + hi->length = 0; + hi->length_target = is_variable ? Random(10, 30) : 20; + hi->velocity_limit = is_variable ? RandomDouble(0.04, 0.08) : 0.07; + hi->displacement_limit = 10; + hi->acceleration_limit = is_variable ? RandomDouble(0.02, 0.04) : 0.03; + hi->head_radius = is_variable ? RandomDouble(0.18, 0.25) : 0.2; + hi->tail_radius = is_variable ? RandomDouble(0.08, 0.15) : 0.15; + hi->lit = false; + hi->bright_suppress = RandomDouble(0.3, 0.55); + CoordUnitRandom(&(hi->velocity)); + CoordSetAmplitude(&(hi->velocity), hi->velocity_limit); + } else Cerr(":%s: calloc failed - %s\n", __FUNCTION__, strerror(errno)); + SpermLonger(hi); + return hi; + } + + void SpermDelete(Sperm_t **doomed_addr) + { + if(doomed_addr) + { + Sperm_t *doomed = *doomed_addr; + if(doomed) + { + if(doomed->segments) + SegmentDeleteAll(&(doomed->segments)); + if(doomed->next) + SpermDelete(&(doomed->next)); + free(doomed); + *doomed_addr = 0; + } else Cerr(":%s: called on null Sperm_t via addr %#lx\n", + __FUNCTION__, (unsigned long int)doomed); + } else Cerr(":%s: called with null parm or parm target\n", __FUNCTION__); + } + + unsigned int SpermLength(Sperm_t *s) + { + return s->length; + } + + void SpermLonger(Sperm_t *s) /* lengthen by one segment, the new head */ + { + static Coord_t origin = {0,0,0}; + Segment_t *newseg = 0; + if(SpermLength(s) == 0) + newseg = SegmentNew(&origin); + else { + Segment_t *oldhead = s->segments; + Coord_t new_position = oldhead->position; /* struct copy */ + CoordPlusEquals(&new_position, &(s->velocity)); + newseg = SegmentNew(&new_position); + } + if(s->segments) s->segments->prev = newseg; /* point head's prev to new */ + newseg->next = s->segments; /* push newseg onto head of sperm`s segments */ + s->segments = newseg; + if( ! s->segment_last) s->segment_last = newseg; /* insure tail ptr */ + ++(s->length); + } + + void SpermShorter(Sperm_t *s) + { + if(s->segment_last) /* don`t bother if no tail is recorded */ + { + Segment_t *doomed = s->segment_last; + if(s->segments == doomed) + s->segments = doomed->next; + s->segment_last = doomed->prev; + SegmentExtract(doomed);/* automatically does neighbor next/prevs */ + SegmentDeleteAll(&doomed); /* tail remains, since doomed`s extracted */ + --(s->length); + } + } + + void SpermUpdate(Sperm_t *s) + { + int length = SpermLength(s); + if(length > 0) + { + Coord_t acceleration; /* jitter the velocity */ + Coord_t newpos; /* hypothetical next position */ + CoordUnitRandom(&acceleration); + CoordSetAmplitude(&acceleration, s->acceleration_limit); + CoordPlusEquals(&(s->velocity), &acceleration); + CoordSetAmplitude(&(s->velocity), s->velocity_limit); + newpos = s->segments->position; + CoordPlusEquals(&newpos, &(s->velocity)); + if(CoordAmplitude(&newpos) > s->displacement_limit) + CoordInvert(&(s->velocity)); + + SpermLonger(s); + if(length > s->length_target) + SpermShorter(s); + } + } + + bool SpermLightDetails(Sperm_t *s, Coord_t *pos, Coord_t *dir) + { + bool succp = false; + if(s && pos && dir) + { + Coord_t *head = 0; + Coord_t *tail = 0; + if(s->segments) head = &(s->segments->position); + if(s->segment_last) tail = &(s->segment_last->position); + if(head && tail) + { + Coord_t bulb = *head; /* struct assign */ + CoordMinusEquals(&bulb, tail); /* now this is the direction */ + *dir = bulb; + CoordPlusEquals(&bulb, head); /* now it`s the bulb`s position */ + *pos = bulb; + succp = true; + } + } else Cerr(":%s: parms (%#lx, %#lx, %#lx) not all valid\n", + __FUNCTION__, + (unsigned long int)s, + (unsigned long int)pos, (unsigned long int)dir); + return succp; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - World */ + + void WorldOutF(World_t *w, FILE *out) + { + fprintf(out, "World %#lx\n", (unsigned long int)w); + fprintf(out, " sky radius %f\n", w->sky_radius); + fprintf(out, " swarm %#lx, population %d (out of %d desired)\n", + (unsigned long int)w->swarm, w->population, w->desired_count); + fprintf(out, " lights %d (out of %d desired)\n", + WorldLightCount(w), w->desired_lit_count); + fprintf(out, " light level %f, min %f, max %f\n", + w->light_level, w->light_min, w->light_max); + fprintf(out, " night bias %f\n", w->night_bias); + fprintf(out, " seconds in day %ld, timebase %ld, cycle point %f", + w->seconds_in_day, w->timebase, w->cycle_point); + { + enum { bufmax = 64 }; + char buf[bufmax + 1]; + struct tm *tv = localtime((time_t*)&(w->timebase)); + if(0 < strftime(&buf[0], bufmax, "%Y-%m-%d %H:%M:%S %Z", tv)) + fprintf(out, " (%s)", &buf[0]); + } + fprintf(out, "\n"); + } + + World_t *WorldNew(void) + { + World_t *hi = (World_t*)calloc(1, sizeof *hi); + if(hi) + { + hi->desired_count = 100; + hi->sky_radius = 20; + hi->desired_lit_count = 7; + hi->light_min = 0; /* redundant, but pairs with light_max */ + hi->light_max = 1.0; + hi->night_bias = 0.50; + hi->seconds_in_day = 60; + } else Cerr(":%s: calloc failed - %s\n", __FUNCTION__, strerror(errno)); + return hi; + } + + void WorldDelete(World_t **doomed_addr) + { + if(doomed_addr && *doomed_addr) + { + free(*doomed_addr); + *doomed_addr = 0; + } + } + + unsigned int WorldPopulation(World_t *w) + { + unsigned int population = 0; + Sperm_t *s = 0; + for(s = w->swarm ; s ; s = s->next) + ++population; + return population; + } + + int WorldLightCount(World_t *w) + { + static int count = 0; + int i; + if(0 == count) + { + for(i = 0 ; i < 1024 ; ++i) /* 1024 to avoid runaway loop */ + if(WorldLightForIndex(w, i)) + ++count; + else + break; + } + return count; + } + + GLenum WorldLightForIndex(World_t *w, int index) + { + GLenum result = GL_NONE; + static const GLenum lights[] = { + GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, GL_LIGHT4, + GL_LIGHT5, GL_LIGHT6, GL_LIGHT7 /* these numbers are all offset by 1 */ + }; + enum { lightcount = sizeof lights / sizeof lights[0] }; + if(index < lightcount) + result = lights[index]; + return result; + } + + int WorldLitCount(World_t *w) + { + int count = 0; + Sperm_t *s = 0; + for(s = w->swarm ; s ; s = s->next) + if(s->lit) + ++count; + return count; + } + + void WorldUpdate(World_t *w) + { + if(w->population < w->desired_count) { + Sperm_t *wiggler = SpermNew(w->is_sperm_variable); + if(wiggler) + { + if(WorldLitCount(w) < w->desired_lit_count) + wiggler->lit = true; + wiggler->next = w->swarm, w->swarm = wiggler; + ++(w->population); + } + } else if(w->population > w->desired_count) { + if(w->swarm) /* we`d better have a swarm to reduce */ + { + Sperm_t *doomed = 0; + Sperm_t *s = 0, *prior = 0; /* look for an unlit one 1st */ + for(s = w->swarm ; s ; prior = s, s = s->next) + { + if( ! s->lit) /* ah! move it to the head of w->swarm */ + { + if(prior) /* if not, it -is- the head; nothing to do */ + { + prior->next = s->next; + s->next = w->swarm; + w->swarm = s; + } + break; + } + } + /* if we didn`t find a better candidate, the head dies anyway */ + doomed = w->swarm; + w->swarm = doomed->next; + doomed->next = 0; + SpermDelete(&doomed); + --(w->population); + } + } else { /* population is at target_pop, but are lights at target? */ + Sperm_t *s = 0; + int lit = WorldLitCount(w); + if(lit < w->desired_lit_count) + for(s = w->swarm ; s ; s = s->next) + { + if( ! s->lit) + s->lit = true, ++lit; + if(lit == w->desired_lit_count) + break; + } + } + if(WorldLitCount(w) > w->desired_lit_count) + { + Sperm_t *s = 0; + int lit = WorldLitCount(w); + for(s = w->swarm ; s ; s = s->next) + { + if(s->lit) + { + s->lit = false; + --lit; + } + if(lit <= w->desired_lit_count) + break; + } + } + + { /* compute current light level, including effect of night_bias */ + /* cycle point 0 = dawn, 0.25 = noon, etc. */ + unsigned long int seconds; + unsigned long int microseconds = MicroAndSeconds(&seconds); + unsigned long int whole_sec_in_cycle = ((seconds - w->timebase) + % w->seconds_in_day); + double sec_in_cycle = (double)whole_sec_in_cycle + (microseconds + / 1000000.0); + double light_range = w->light_max - w->light_min; /* [0,1] */ + w->cycle_point = sec_in_cycle / w->seconds_in_day; /* [0,1) */ + w->light_level = ((sin(w->cycle_point *2*M_PI) + 1.0) / 2.0) /*[0,1]*/ + * light_range + w->light_min; + } + + { + Sperm_t *s = 0; + Sperm_t *last = 0; + for(s = w->swarm ; s ; last = s, s = (s ? s->next : w->swarm)) + { + /* do the normal update */ + SpermUpdate(s); + if(w->is_strayer_hostile) + { + /* hmm, what happens if we delete straying ones? */ + if(s->segments + && (CoordAmplitude(&s->segments->position) + > (0.95 * s->displacement_limit))) + { + Sperm_t *doomed = s; + if(last) + { + last->next = doomed->next; /* connect around doomed */ + s = last; /* don`t use use s until next iteration */ + } + if(w->swarm == doomed) + w->swarm = doomed->next; /* insure valid head */ + doomed->next = 0; + SpermDelete(&doomed); + --(w->population); + } + } + /* NOTE: (s) may be off swarm's end, so don`t add code below */ + } + } + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - Functions - X/GL mgmt */ + + char *MakeTitleString(void) + { + char *result = 0; + static char string[128] = "GLSperm: from the XScreenSaver "; + /* example of screensaver_id: + * "@(#)xscreensaver 4.03 (30-Apr-2002), by Jamie Zawinski (jwz@jwz.org)" + * we want -> +--+ +---------+ + * for "GLSperm: from the XScreenSaver 4.03 distribution (30-Apr-2002)" + */ + char *string_to_parse = strdup(screensaver_id); + char *version = 0; + char *datever = 0; /* date of version */ + char *paren = 0; + char *space = 0; /* er 4.03 (30-Apr-2002), by J */ + if( ! string_to_parse) + { + Cerr(":%s:strdup failed - %s\n", __FUNCTION__, strerror(errno)); + return result; + } + if((version = strchr(string_to_parse, ' ')))/* v */ + { /* | */ + if((paren = strchr(++version, ')'))) /* v c */ + { /* | | */ + paren[0] = '\0'; /* v 0 */ + if((space = strchr(version, ' ')))/* v s c */ + { + datever = space + 2; /* v s d----------0 */ + *space = '\0'; /* v---0 d----------0 */ + strcat(&string[0], version); + strcat(&string[0], " distribution ("); + strcat(&string[0], datever); + strcat(&string[0], ")"); + result = &string[0]; + } else Cerr(":%s: problem finding ' ' in \"%s\"\n", + __FUNCTION__, version); + } else Cerr(":%s: problem finding ')' in \"%s\"\n", + __FUNCTION__, version); + } else Cerr(":%s: problem finding ' ' in \"%s\"\n", + __FUNCTION__, screensaver_id); + free(string_to_parse); + return result; + } + + /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Main */ + + void VersionOut(FILE *out) + { + int veri = 0; + fprintf(out, "Version information on %s\n", progname); + for(veri = 0 ; Version[veri] ; ++veri) + fprintf(out, " %s\n", Version[veri]); + } + + void UsageOut(FILE *out) + { + fprintf + (out, "%s: %s", progname, + " [-h] | [