/* The Solar System, simulated on the web. */ #include #include #include #include #include #include #include "cgic.h" #include "gd.h" #include "gdfontt.h" #define OBJECTS_FILE "/CHANGE/THIS/FILE/sss/objects.dat" #define PROGRAM_URL "/CHANGE/THIS/URL/cgi-bin/nph-sss" #define PROGRAM_DATA_PATH "/CHANGE/THIS/PATH/sss/" int windowSize = 200; typedef struct { double x,y; double vx,vy; double mass; double radius; char name[20]; } object; int objectsTotal = 0; double gConstant; double maxX; double maxY; double minX; double minY; double width; double magnification = 1.0; #define OBJECTS_MAX 100 object objects[OBJECTS_MAX]; #define SCALEX(x) (((x-minX)/(maxX-minX))*windowSize) #define UNSCALEX(x) ((x*(maxX-minX)/windowSize)+minX) #define SCALEY(y) (((y-minY)/(maxY-minY))*windowSize) #define UNSCALEY(y) ((y*(maxY-minY)/windowSize)+minY) #ifndef PI #define PI 3.141592653 #endif /* PI */ int Page(int firstTime); int Canvas(); int Select(); int ZoomIn(); int ZoomOut(); int TimePlus(); int TimeMinus(); int Resume(); int Nemesis(); int UnCenter(); int SizePlus(); int SizeMinus(); int Form(); void AccountForCenter(); void ObjectsSetup(); int sessionId; void SelectSessionId(); void WriteSessionState(); void ReadSessionState(); void ParseSessionId(); char *pathInfoP; int cgiMain() { int i; pathInfoP = cgiPathInfo; ParseSessionId(); if (!strcmp(pathInfoP, "/canvas.gif")) { return Canvas(); } else if (!strcmp(pathInfoP, "/form")) { return Form(); } else if (!strcmp(pathInfoP, "/zoomin")) { return ZoomIn(); } else if (!strcmp(pathInfoP, "/zoomout")) { return ZoomOut(); } else if (!strcmp(pathInfoP, "/timeplus")) { return TimePlus(); } else if (!strcmp(pathInfoP, "/timeminus")) { return TimeMinus(); } else if (!strcmp(pathInfoP, "/sizeplus")) { return SizePlus(); } else if (!strcmp(pathInfoP, "/sizeminus")) { return SizeMinus(); } else if (!strcmp(pathInfoP, "/uncenter")) { return UnCenter(); } else if (!strcmp(pathInfoP, "/nemesis")) { return Nemesis(); } else if (!strcmp(pathInfoP, "/select")) { return Select(); } else if (!strcmp(pathInfoP, "/resume")) { return Resume(); } else { return Page(1); } } int centerFloating = 0; int centerFloatingObject; double timeStep=0.5; int Page(int firstTime) { int i; if (firstTime) { SelectSessionId(); ObjectsSetup(); WriteSessionState(); } fprintf(cgiOut, "HTTP/1.0 200 OK"); fprintf(cgiOut, "Pragma: no-cache\n"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "Solar System Simulator\n"); fprintf(cgiOut, "

Solar System Simulator

\n"); fprintf(cgiOut, "Click on any planet to center the display\n"); fprintf(cgiOut, "on that object, or select one of the options below.\n

"); fprintf(cgiOut, "Notes: zoom in to watch the inner solar system,\n"); fprintf(cgiOut, "or increase the time step many times to make the outer planets\n"); fprintf(cgiOut, "more interesting to watch.\n"); fprintf(cgiOut, "Large time steps will destabilize the moon.\n"); fprintf(cgiOut, "Even larger time steps may confuse the planets as well.

\n"); fprintf(cgiOut, "The stop button can be used to pause the simulation. Select\n"); fprintf(cgiOut, "Resume to resume the simulation after a stop.

"); /* Imagemap link */ fprintf(cgiOut, "
\n", PROGRAM_URL, sessionId); /* Image */ fprintf(cgiOut, "\n", PROGRAM_URL, sessionId); fprintf(cgiOut, "

\n"); /* Status display */ if (centerFloating) { fprintf(cgiOut, "Centered on: %s ", objects[centerFloatingObject].name); } fprintf(cgiOut, "Magnification: %fx ", magnification); fprintf(cgiOut, "Time Step: %f days
", timeStep); /* Various controls */ fprintf(cgiOut, "Zoom In x2 ", PROGRAM_URL, sessionId); fprintf(cgiOut, "Zoom Out x2 ", PROGRAM_URL, sessionId); fprintf(cgiOut, "Time Step x2 ", PROGRAM_URL, sessionId); fprintf(cgiOut, "Time Step /2 ", PROGRAM_URL, sessionId); /* This code works, but Netscape is very difficult to use if the window is any larger than 200x200, at least on my system. Feel free to remove the #if/#endif pair and see what happens. */ #if 0 if (windowSize < 1600) { fprintf(cgiOut, "Window Size x2 ", PROGRAM_URL, sessionId); } if (windowSize > 25) { fprintf(cgiOut, "Window Size /2 ", PROGRAM_URL, sessionId); } #endif fprintf(cgiOut, "NEMESIS ", PROGRAM_URL, sessionId); fprintf(cgiOut, "Restart ", PROGRAM_URL); fprintf(cgiOut, "Resume ", PROGRAM_URL, sessionId); if (centerFloating) { fprintf(cgiOut, "Uncenter", PROGRAM_URL, sessionId); } fprintf(cgiOut, "

\n"); /* A form, for skilled users */ fprintf(cgiOut, "


Use this form if you wish to make several changes quickly.

\n"); fprintf(cgiOut, "

\n", PROGRAM_URL, sessionId); fprintf(cgiOut, "
\n"); fprintf(cgiOut, " Magnification
\n", magnification); fprintf(cgiOut, " Time Step (days)
\n", timeStep); fprintf(cgiOut, "Center Display On:
"); fprintf(cgiOut, "\n
"); fprintf(cgiOut, "
\n"); fprintf(cgiOut, "\n"); return 0; } int Canvas() { int i, j; int x,y; int done = 0; int labels = 0; int trails = 0; gdImagePtr im; int black; int white; int blue; fprintf(cgiOut, "HTTP/1.0 200 OK\n"); cgiHeaderContentType( "multipart/x-mixed-replace;boundary=goober"); while (!done) { /* Re-read the session state, do the computations as quickly as possible, and write the state again. */ ReadSessionState(); for (i=0; (i < objectsTotal); i++) { double x,y; for (j=0; (j < objectsTotal); j++) { if (i != j) { double dist; double pull; dist = hypot( objects[i].x - objects[j].x, objects[i].y - objects[j].y); if (dist != 0.0) { pull=(gConstant * objects[j].mass / (dist * dist)); objects[i].vx += (objects[j].x - objects[i].x) / dist * pull * timeStep; objects[i].vy += (objects[j].y - objects[i].y) / dist * pull * timeStep; } } } } for (i=0; (i < objectsTotal); i++) { objects[i].x += (objects[i].vx * timeStep); objects[i].y += (objects[i].vy * timeStep); } WriteSessionState(); AccountForCenter(); /* Now build a GIF */ im = gdImageCreate(windowSize, windowSize); black = gdImageColorAllocate(im, 0, 0, 0); white = gdImageColorAllocate(im, 255, 255, 255); blue = gdImageColorAllocate(im, 192, 192, 255); for (i=0; (iradius); int x = SCALEX(o->x); int y = SCALEY(o->y); gdImageArc(im, x, y, val, val, 0, 360, white); gdImageString(im, gdFontTiny, x - (gdFontTiny->w * strlen(o->name) / 2), y + val, o->name, blue); } sleep(1); fprintf(cgiOut, "\n--goober\n"); fprintf(cgiOut, "Content-type: image/gif\n\n"); gdImageGif(im, cgiOut); gdImageDestroy(im); fflush(cgiOut); } fprintf(cgiOut, "\n--goober--\n"); return 0; } void CenterClosest(int x, int y); int Select() { int x, y; ReadSessionState(); if (sscanf(cgiQueryString, "%d,%d", &x, &y) != 2) { cgiHeaderContentType("text/html"); fprintf(cgiOut, "HTTP/1.0 200 OK\n"); fprintf(cgiOut, "

Bad Click: %s

\n", cgiQueryString); return 0; } CenterClosest(x, y); WriteSessionState(); return Page(0); } int UnCenter() { ReadSessionState(); centerFloating = 0; WriteSessionState(); return Page(0); } int ZoomIn() { ReadSessionState(); magnification *= 2.0; WriteSessionState(); return Page(0); } int ZoomOut() { ReadSessionState(); magnification /= 2.0; WriteSessionState(); return Page(0); } int TimePlus() { ReadSessionState(); timeStep *= 2.0; WriteSessionState(); return Page(0); } int TimeMinus() { ReadSessionState(); timeStep /= 2.0; WriteSessionState(); return Page(0); } int SizePlus() { ReadSessionState(); if (windowSize < 200) { windowSize *= 2; } WriteSessionState(); return Page(0); } int SizeMinus() { ReadSessionState(); if (windowSize) { windowSize /= 2; } WriteSessionState(); return Page(0); } int Resume() { ReadSessionState(); return Page(0); } int Nemesis() { double largestMass = 1.0, largestRadius = 1.0; int i; object *o; ReadSessionState(); if (objectsTotal == OBJECTS_MAX) { /* Don't break the simulation */ return 0; } /* Wreak havoc: introduce an object as large as the largest object in the system and send it whizzing through. */ for (i=0; (i < objectsTotal); i++) { if ((!i) || (objects[i].mass > largestMass)) { largestMass = objects[i].mass; } if ((!i) || (objects[i].radius > largestRadius)) { largestRadius = objects[i].radius; } } o = &objects[objectsTotal]; /* Start in the upper left corner of the simulation */ o->x = minX; o->y = minY; /* Fast enough to cross in 365 days */ o->vx = (maxX - minX) / 365.0; o->vy = (maxY - minY) / 365.0; /* Very big, very nasty */ o->mass = largestMass * 2; o->radius = largestRadius * 2; strcpy(o->name, "NEMESIS"); objectsTotal++; WriteSessionState(); return Page(0); } void SelectSessionId() { FILE *in; FILE *out; char s[256]; /* Come up with a new, never-before-used session id. */ sprintf(s, "%s/id", PROGRAM_DATA_PATH); in = fopen(s, "r"); if (!fscanf(in, "%d\n", &sessionId)) { sessionId = 0; } fclose(in); out = fopen(s, "w"); fprintf(out, "%d\n", sessionId+1); fclose(out); } void ParseSessionId() { if (!strncmp(pathInfoP, "/ID:", 4)) { char *next; sessionId = atoi(pathInfoP + 4); next = strchr(pathInfoP + 4, '/'); if (next) { pathInfoP = next; } else { pathInfoP = ""; } } } void CenterClosest(int x, int y) { double smallestDist; int i; double centerX, centerY; centerFloating = 1; AccountForCenter(); centerX = UNSCALEX(x); centerY = UNSCALEY(y); for (i=0; (i= 0) && (center < objectsTotal)) { centerFloating = 1; centerFloatingObject = center; } WriteSessionState(); return Page(0); } void ObjectsSetup(void) { FILE* in; object *o; char line[256]; objectsTotal = 0; if (!(in = fopen(OBJECTS_FILE, "r"))) { exit(1); } do { if (!fgets(line, 256, in)) { return; } } while ((line[0] == '#') || (line[0] == '\n')); gConstant = atof(line); while (!feof(in)) { int ch; o = &objects[objectsTotal]; if (!fgets(line, 256, in)) { break; } if ((line[0] == '#') || (line[0] == '\n')) { continue; } if (sscanf(line,"%20s %lf %lf %lf %lf %lf %lf", o->name, &(o->x), &(o->y), &(o->vx), &(o->vy), &(o->mass), &(o->radius)) < 7) { continue; } objectsTotal++; if (objectsTotal == OBJECTS_MAX) { break; } } fclose(in); } void WriteSessionState() { FILE *out; char sold[256], snew[256]; int i; /* Write to a different filename initially, then delete the old and rename the new at the end. This reduces the probability that an untimely kill signal will cause problems. */ sprintf(sold, "%s/%d.sav", PROGRAM_DATA_PATH, sessionId); sprintf(snew, "%s/%d.dtn", PROGRAM_DATA_PATH, sessionId); out = fopen(snew, "w"); if (!out) { /* Can't access memo file */ return; } fprintf(out, "%d\n", objectsTotal); fprintf(out, "%e\n", gConstant); fprintf(out, "%d\n", windowSize); fprintf(out, "%d\n", centerFloating); fprintf(out, "%d\n", centerFloatingObject); fprintf(out, "%e\n", timeStep); fprintf(out, "%e\n", magnification); for (i=0; (i < objectsTotal); i++) { object *o = &objects[i]; fprintf(out, "%e %e %e %e %e %e %20s\n", o->x, o->y, o->vx, o->vy, o->mass, o->radius, o->name); } fclose(out); /* OK, swap the files quickly. */ unlink(sold); rename(snew, sold); } void ReadSessionState() { FILE *in; char s[256]; int i; maxX=0; minX=0; maxY=0; minY=0; sprintf(s, "%s/%d.sav", PROGRAM_DATA_PATH, sessionId); in = fopen(s, "r"); if (!in) { /* Can't access memo file */ return; } fscanf(in, "%d\n", &objectsTotal); fscanf(in, "%lf\n", &gConstant); fscanf(in, "%d\n", &windowSize); fscanf(in, "%d\n", ¢erFloating); fscanf(in, "%d\n", ¢erFloatingObject); fscanf(in, "%lf\n", &timeStep); fscanf(in, "%lf\n", &magnification); for (i=0; (i < objectsTotal); i++) { object *o = &objects[i]; fscanf(in, "%lf %lf %lf %lf %lf %lf %s\n", &o->x, &o->y, &o->vx, &o->vy, &o->mass, &o->radius, &o->name); if (o->x < minX || !objectsTotal) { minX = o->x; } if (o->x > maxX || !objectsTotal) { maxX = o->x; } if (o->y < minY || !objectsTotal) { minY = o->y; } if (o->y > maxY || !objectsTotal) { maxY = o->y; } } fclose(in); if ((-maxX) < minX) { minX = -maxX; } else { maxX = -minX; } if ((-maxY) < minY) { minY = - maxY; } else { maxY = - minY; } if (minX < minY) { minY = minX; } else { minX = minY; } if (maxX > maxY) { maxY = maxX; } else { maxX = maxY; } width = (maxX - minX); }