/* ifs.c - Iterated fractal generator - Don Yang (uguu.org) MSVC users: Do not compile with register calling (-Gr) unless you want ifs to die in mysterious ways. Assume sizeof(int) == 4, sizeof(short) == 2 Keys (mouse button 2 will show all the keys): a: Add random fractal rule d: Delete selected fractal rule e: Delete all fractal rules u: Undo last change l: Reload state q: Exit program [: Rotate 2341 (cycle rule vertices forward) ]: Rotate 4123 (cycle rule vertices backward) (: Flip 2143 (mirror rule vertices by consecutive pairs) ): Flip 4321 (mirror rule vertices by inner/outer pairs) {: Fold 1432 (exchange even rule vertices along diagonal) }: Fold 3214 (exchange odd rule vertices along diagonal) 0: Toggle quality 1: Toggle rule display 2: Toggle fractal display 3: Toggle background bitmap display 4: Enable addictive blend: GL_SRC_ALPHA/GL_ONE 5: Disable addictive blend: GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA 6: Set window size to 1x background bitmap size (default = 320x240) 7: Set window size to 2x background bitmap size (default = 640x480) 8: Set window size to 4x background bitmap size (default = 1280x960) 9: Toggle density / dot count (4x speed difference) p: Toggle zoom boundary mode f: Set background bitmap intensity to 1.0 h: Set background bitmap intensity to 0.5 t: Set background bitmap intensity to 0.3 x: Adjust width to fit background bitmap aspect ratio z: Adjust height to fit background bitmap aspect ratio r: Set fractal color to red g: Set fractal color to green b: Set fractal color to blue c: Set fractal color to cyan m: Set fractal color to magenta y: Set fractal color to yellow w: Set fractal color to white k: Set fractal color to black i: Use inverted background (white background) n: Use normal background (black background) Command line: -c Use alternate config file. This saves the trouble of renaming multiple cfg files and stuff, if you ever want to start collecting IFS images. A full featured editor would have file selector and all that stuff, which I don't have time code. -b Load uncompressed 8bit/24bit windows BMP to background. Window size will be adjusted to reflect image size (which is hopefully some reasonable size), and extra menu options will be available. This may be helpful in tracing certain images. -n Do not save config file on exit. Due to the differences in graphics hardware/software, I can't guarantee that everything will be visible under certain color/blending combinations (i.e. if you see a complete white screen). In those cases, play with blending modes may help. 08/06/00: 1.0 (initial) 07/17/01: 1.1 (PostScript support) 11/19/03: 1.2 (density, rotate vertices, zoom edit) */ /*@ -compdef -realcompare -usedef @*/ /*@ -globstate -modobserver -onlytrans -statictrans @*/ /* Headers */ #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include /* Constants */ #define DEFAULT_CONFIG_FILE "ifs.cfg" #define MAX_BMP_WIDTH 1024 #define MAX_BMP_HEIGHT 1024 #define BMP_WIDTH_OFFSET 18 #define BMP_HEIGHT_OFFSET 22 #define BMP_BIT_OFFSET 28 #define BMP_COLOR_OFFSET 46 #define BMP_CLUT_OFFSET 54 #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 #define SCREEN_POS_X 100 #define SCREEN_POS_Y 100 #define PS_MINX 72 #define PS_MINY 72 #define PS_MAXX 540 #define PS_MAXY 720 #define PS_POINT_SIZE 1 #define PICK_BUFFER_SIZE 256 #define PICK_TOLERANCE 20.0 #define POINT_TOLERANCE 0.05 #define MAX_IFS_RULES 32 #define FIXED_RAND_SEED 1337 #define DOT_COUNT 128 #define ITERATION_COUNT 128 #define INIT_ITER_COUNT 20 #define IFS_STACK_SIZE 8 #define MAX_PARAM_LIMIT 2.1 #define MIN_PARAM_LIMIT -1.1 #define OPT_QUALITY 16 #define OPT_DISPLAY_RULE 32 #define OPT_DISPLAY_FRACTAL 64 #define OPT_ADDITIVE_MODE 128 #define OPT_RENDER_INVERTED 256 #define OPT_DENSITY 512 #define OPT_FRACTAL_COLOR 15 enum { FRACTAL_COLOR_R = 0, FRACTAL_COLOR_G, FRACTAL_COLOR_B, FRACTAL_COLOR_C, FRACTAL_COLOR_M, FRACTAL_COLOR_Y, FRACTAL_COLOR_W, FRACTAL_COLOR_K, GUIDE_COLOR, OUTLINE_COLOR, GUIDE_COLOR_I, OUTLINE_COLOR_I, SELECTED_COLOR, MODIFY_COLOR, BOUNDARY_COLOR }; /* Types */ typedef struct { GLdouble x1, y1, x2, y2, x4, y4; GLdouble a, b, c, d, e, f, x3, y3; GLuint list; int rebuild; } IFSRule; /* Globals */ static char *ConfigFile, *PSFile; static int SaveOnExit; static /*@observer@*/char *BGFile; static int DisplayBackground; static int BGWidth, BGHeight; static GLuint Background; static GLfloat BGIntensity; static int Window, Zoomed; static int MousePressed; static int MouseX, MouseY; static int Selected, Point; static int Quality, Density; static int DisplayRule, DisplayFractal; static int FractalColor; static int AdditiveBlend; static int RenderInverted; static int RuleCount; static int Rebuild, Modified; static GLuint Fractal; static IFSRule Rule[MAX_IFS_RULES]; static GLfloat Palette[15][4] = { {1.0f, 0.7f, 0.7f, 0.5f}, /* FRACTAL_COLOR_R */ {0.7f, 1.0f, 0.7f, 0.5f}, /* FRACTAL_COLOR_G */ {0.7f, 0.7f, 1.0f, 0.5f}, /* FRACTAL_COLOR_B */ {0.7f, 1.0f, 1.0f, 0.5f}, /* FRACTAL_COLOR_C */ {1.0f, 0.7f, 1.0f, 0.5f}, /* FRACTAL_COLOR_M */ {1.0f, 1.0f, 0.7f, 0.5f}, /* FRACTAL_COLOR_Y */ {1.0f, 1.0f, 1.0f, 0.5f}, /* FRACTAL_COLOR_W */ {0.0f, 0.0f, 0.0f, 0.5f}, /* FRACTAL_COLOR_K */ {1.0f, 1.0f, 1.0f, 0.7f}, /* GUIDE_COLOR */ {0.0f, 0.0f, 0.0f, 0.5f}, /* OUTLINE_COLOR */ {0.0f, 0.0f, 0.0f, 0.7f}, /* GUIDE_COLOR_I */ {1.0f, 1.0f, 1.0f, 0.5f}, /* OUTLINE_COLOR_I */ {0.0f, 1.0f, 0.0f, 0.9f}, /* SELECTED_COLOR */ {1.0f, 0.0f, 0.0f, 0.9f}, /* MODIFY_COLOR */ {0.5f, 0.0f, 0.0f, 0.9f} }; /* BOUNDARY_COLOR */ static IFSRule UndoStack[IFS_STACK_SIZE][MAX_IFS_RULES]; static IFSRule TmpRule[MAX_IFS_RULES]; static int UndoCount[IFS_STACK_SIZE], TmpCount; static int UndoStackIndex, UndoStackStart; static int RandomSeed; /* Prototypes */ static void AddRule(void); static void Display(void); static void DrawFractals(void); static void DrawRule(int index); static void InitGraphics(void); static void Keyboard(unsigned char c, /*@unused@*/int u, /*@unused@*/int v); static void LoadBackground(char *name); static int LoadConfig(void); static void Menu(int action); static void MouseButton(int button, int state, int x, int y); static void MouseMotion(int x, int y); static void Orientate(int a, int b, int d); static int Pick(int x, int y, int o); static void Quit(void); static void RemoveRule(void); static void Reset(void); static void Reshape(/*@unused@*/int w, /*@unused@*/int h); static void SaveConfig(void); static void SavePostScript(void); static void SaveState(IFSRule dst[], IFSRule src[]); static void SaveUndo(IFSRule source[], int count); static void Undo(void); /******************************************************************** main */ int main(int argc, char **argv) { /*@observer@*/char *c; int i; (void)puts("IFS 1.2 (11/19/03) - Don Yang (http://uguu.org)\n"); /* Initialize OpenGL */ glutInit(&argc, argv); /* Parse command line */ Background = 0; c = BGFile = ConfigFile = PSFile = NULL; SaveOnExit = 1; for(i = 1; i < argc; i++) { if( argv[i][0] == '-' ) { if( i + 1 < argc ) { if( tolower(argv[i][1]) == (int)'c' ) { c = argv[++i]; continue; } else if( tolower(argv[i][1]) == (int)'b' ) { BGFile = argv[++i]; continue; } } else if( tolower(argv[i][1]) == (int)'n' ) { SaveOnExit = 0; continue; } } /* Command line help */ (void)puts( "ifs [-c file] [-b image]\n\n" "-c Use alternate config file instead of " DEFAULT_CONFIG_FILE "\n" " This also modifies the ps file name\n" "-b Load image (8bit/24bit windows BMP) to background\n" "-n Do not save config/ps file on exit\n"); return 0; } if( c == NULL ) c = DEFAULT_CONFIG_FILE; if( (ConfigFile = (char*)malloc(strlen(c) + 5)) == NULL ) return -1; strcpy(ConfigFile, c); if( (c = strrchr(ConfigFile, '.')) == NULL ) strcat(ConfigFile, ".cfg"); else if( strchr(c, '/') != NULL || strchr(c, '\\') != NULL ) strcat(ConfigFile, ".cfg"); if( (PSFile = (char*)malloc(strlen(ConfigFile) + 4)) == NULL ) return -1; strcpy(strrchr(strcpy(PSFile, ConfigFile), '.'), ".ps"); /* Start program and loop forever */ InitGraphics(); glutMainLoop(); return 0; } /* main() */ /***************************************************************** AddRule */ static void AddRule(void) { int x1, y1, x2, y2, x4, y4; if( RuleCount >= MAX_IFS_RULES ) return; /* Save undo state */ SaveUndo(Rule, RuleCount); /* Generate random rule */ srand((unsigned)RandomSeed); x1 = rand(); x2 = rand(); x4 = rand(); y1 = rand(); y2 = rand(); y4 = rand(); RandomSeed = rand(); Rule[RuleCount].x1 = (x1 & 2047) / 2047.0; Rule[RuleCount].y1 = (y1 & 2047) / 2047.0; Rule[RuleCount].x2 = (x2 & 2047) / 2047.0; Rule[RuleCount].y2 = (y2 & 2047) / 2047.0; Rule[RuleCount].x4 = (x4 & 2047) / 2047.0; Rule[RuleCount].y4 = (y4 & 2047) / 2047.0; Rule[RuleCount++].rebuild = Rebuild = 1; /* Select newly generated rule */ Selected = RuleCount - 1; Point = 0; } /* AddRule() */ /***************************************************************** Display */ static void Display(void) { int i; /* Initialize window */ glutSetWindow(Window); glDrawBuffer(GL_BACK); if( RenderInverted != 0 ) glClearColor(1.0f, 1.0f, 1.0f, 0.0f); else glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_ALPHA_TEST); glDisable(GL_DEPTH_TEST); glDisable(GL_NORMALIZE); /* Initialize environment */ if( Quality != 0 ) { glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glEnable(GL_POINT_SMOOTH); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glPointSize(2.0f); } else { glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glPointSize(1.0f); } /* Initialize viewport */ glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if( Zoomed != 0 ) gluOrtho2D(-0.5, 1.5, -0.5, 1.5); else gluOrtho2D(0.0, 1.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw boundary */ if( Zoomed != 0 ) { glColor4fv(Palette[BOUNDARY_COLOR]); glBegin(GL_LINE_LOOP); glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glEnd(); } /* Draw background image */ if( Background != 0 && DisplayBackground != 0 ) { glEnable(GL_TEXTURE_2D); glBlendFunc(GL_ONE, GL_ZERO); glBindTexture(GL_TEXTURE_2D, Background); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glColor3f(BGIntensity, BGIntensity, BGIntensity); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 0.0f, 0.0f); glEnd(); glDisable(GL_TEXTURE_2D); } /* Draw particles */ if( DisplayFractal != 0 ) { glBlendFunc(GL_SRC_ALPHA, (GLenum)(AdditiveBlend != 0 ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA)); glColor4fv(Palette[FractalColor]); if( Rebuild != 0 ) { glNewList(Fractal, GL_COMPILE_AND_EXECUTE); DrawFractals(); glEndList(); Rebuild = 0; } else { glCallList(Fractal); } } /* Draw rules */ if( DisplayRule != 0 ) { /* Draw outline */ glColor4fv( Palette[RenderInverted != 0 ? OUTLINE_COLOR_I : OUTLINE_COLOR]); glBlendFunc(GL_SRC_COLOR, GL_ZERO); glLineWidth(5.0f); for(i = 0; i < RuleCount; i++) { if( Rule[i].rebuild != 0 ) { glNewList(Rule[i].list, GL_COMPILE_AND_EXECUTE); DrawRule(i); glEndList(); Rule[i].rebuild = 0; } else { glCallList(Rule[i].list); } } /* Draw rule */ glBlendFunc(GL_SRC_ALPHA, GL_ONE); glLineWidth(1.5f); for(i = 0; i < RuleCount; i++) { if( i == Selected ) { glColor4fv(Palette[Point != 0 ? MODIFY_COLOR : SELECTED_COLOR]); } else { glColor4fv( Palette[RenderInverted != 0 ? GUIDE_COLOR_I : GUIDE_COLOR]); } glCallList(Rule[i].list); } } /* End */ glutSwapBuffers(); glFlush(); } /* Display() */ /************************************************************ DrawFractals */ static void DrawFractals(void) { double sx, sy, ix; int i, j, r; /* Return if no rules to draw */ if( RuleCount == 0 ) return; /* Initialize rules */ for(i = 0; i < RuleCount; i++) { Rule[i].e = Rule[i].x1; Rule[i].f = Rule[i].y1; Rule[i].a = Rule[i].x2 - Rule[i].e; Rule[i].b = Rule[i].x4 - Rule[i].e; Rule[i].c = Rule[i].y2 - Rule[i].f; Rule[i].d = Rule[i].y4 - Rule[i].f; } /* Trace dots (using fixed random number seed) */ srand(FIXED_RAND_SEED); glBegin(GL_POINTS); for(i = 0; i < DOT_COUNT + (Density != 0 ? DOT_COUNT : 0); i++) { sx = (rand() & 2047) / 2047.0; sy = (rand() & 2047) / 2047.0; for(j = 0; j < INIT_ITER_COUNT; j++) { r = rand() % RuleCount; ix = Rule[r].a * sx + Rule[r].b * sy + Rule[r].e; sy = Rule[r].c * sx + Rule[r].d * sy + Rule[r].f; sx = ix; } for(; j < ITERATION_COUNT + (Density != 0 ? ITERATION_COUNT : 0); j++) { r = rand() % RuleCount; ix = Rule[r].a * sx + Rule[r].b * sy + Rule[r].e; sy = Rule[r].c * sx + Rule[r].d * sy + Rule[r].f; sx = ix; glVertex3d(sx, sy, 0.0); } } glEnd(); } /* DrawFractals() */ /**************************************************************** DrawRule */ static void DrawRule(int index) { Rule[index].x3 = Rule[index].x2 + Rule[index].x4 - Rule[index].x1; Rule[index].y3 = Rule[index].y2 + Rule[index].y4 - Rule[index].y1; glBegin(GL_LINE_LOOP); glVertex3d(Rule[index].x1, Rule[index].y1, 0.0); glVertex3d(Rule[index].x2, Rule[index].y2, 0.0); glVertex3d(Rule[index].x4, Rule[index].y4, 0.0); glEnd(); glBegin(GL_LINE_STRIP); glVertex3d( Rule[index].x3 - 0.1 * (Rule[index].x3 - Rule[index].x4), Rule[index].y3 - 0.1 * (Rule[index].y3 - Rule[index].y4), 0.0); glVertex3d(Rule[index].x3, Rule[index].y3, 0.0); glVertex3d( Rule[index].x3 - 0.3 * (Rule[index].x3 - Rule[index].x2), Rule[index].y3 - 0.3 * (Rule[index].y3 - Rule[index].y2), 0.0); glEnd(); } /* DrawRule() */ /************************************************************ InitGraphics */ static void InitGraphics(void) { int rulemenu, fractalmenu, displaymenu, orientmenu, bgmenu = 0; int i; /* Initialize window */ glutInitDisplayMode((unsigned int)(GLUT_RGBA | GLUT_DOUBLE)); glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT); glutInitWindowPosition(SCREEN_POS_X, SCREEN_POS_Y); Window = glutCreateWindow("ifs"); glutSetWindowTitle("ifs"); glutSetWindow(Window); /* Set hooks */ glutDisplayFunc(Display); glutKeyboardFunc(Keyboard); glutMouseFunc(MouseButton); glutMotionFunc(MouseMotion); glutReshapeFunc(Reshape); /* Load background */ BGWidth = SCREEN_WIDTH / 2; BGHeight = SCREEN_HEIGHT / 2; if( BGFile != NULL ) { LoadBackground(BGFile); if( Background != 0 ) { glutReshapeWindow(BGWidth, BGHeight); } else { BGWidth = SCREEN_WIDTH / 2; BGHeight = SCREEN_HEIGHT / 2; } } /* Generate display lists */ for(i = 0; i < MAX_IFS_RULES; i++) Rule[i].list = glGenLists(1); Fractal = glGenLists(1); /* Generate menus */ rulemenu = glutCreateMenu(Menu); glutAddMenuEntry("[A] Add rule", (int)'a'); glutAddMenuEntry("[D] Delete rule", (int)'d'); glutAddMenuEntry("[E] Delete all rules", (int)'z'); glutAddMenuEntry("[tab] Select next rule", (int)'\t'); glutAddMenuEntry("[U] Undo last change", (int)'u'); glutAddMenuEntry("[1] Toggle rule display", (int)'1'); orientmenu = glutCreateMenu(Menu); glutAddMenuEntry("[[] Rotate 2341", (int)'['); glutAddMenuEntry("[]] Rotate 4123", (int)']'); glutAddMenuEntry("[(] Flip 2143", (int)'('); glutAddMenuEntry("[)] Flip 4321", (int)')'); glutAddMenuEntry("[{] Fold 1432", (int)'{'); glutAddMenuEntry("[}] Fold 3214", (int)'}'); fractalmenu = glutCreateMenu(Menu); glutAddMenuEntry("[R] Red", (int)'r'); glutAddMenuEntry("[G] Green", (int)'g'); glutAddMenuEntry("[B] Blue", (int)'b'); glutAddMenuEntry("[C] Cyan", (int)'c'); glutAddMenuEntry("[M] Magenta", (int)'m'); glutAddMenuEntry("[Y] Yellow", (int)'y'); glutAddMenuEntry("[K] Black", (int)'k'); glutAddMenuEntry("[W] White", (int)'w'); glutAddMenuEntry("[4] Additive blend", (int)'4'); glutAddMenuEntry("[5] Semi-transparent blend", (int)'5'); glutAddMenuEntry("[2] Toggle fractal display", (int)'2'); if( Background != 0 ) { bgmenu = glutCreateMenu(Menu); glutAddMenuEntry("[F] Full intensity", (int)'f'); glutAddMenuEntry("[H] Half intensity", (int)'h'); glutAddMenuEntry("[T] 1/3 intensity", (int)'t'); glutAddMenuEntry("[6] Set window to background size", (int)'6'); glutAddMenuEntry("[7] Set window to 2x background size", (int)'7'); glutAddMenuEntry("[8] Set window to 4x background size", (int)'8'); glutAddMenuEntry("[Z] Set width to fit aspect ratio", (int)'z'); glutAddMenuEntry("[X] Set height to fit aspect ratio", (int)'x'); glutAddMenuEntry("[3] Toggle background display", (int)'3'); } displaymenu = glutCreateMenu(Menu); glutAddMenuEntry("[N] Normal outline/background", (int)'n'); glutAddMenuEntry("[I] Inverted outline/background", (int)'i'); glutAddMenuEntry("[1] Toggle rule display", (int)'1'); glutAddMenuEntry("[2] Toggle fractal display", (int)'2'); if( Background != 0 ) glutAddMenuEntry("[3] Toggle background display", (int)'3'); glutAddMenuEntry("[9] Toggle density", (int)'9'); glutAddMenuEntry("[0] Toggle quality", (int)'0'); glutAddMenuEntry("[P] Toggle zoom boundary", (int)'p'); (void)glutCreateMenu(Menu); glutAddSubMenu("Rules", rulemenu); glutAddSubMenu("Orientation", orientmenu); glutAddSubMenu("Fractals", fractalmenu); if( Background != 0 ) glutAddSubMenu("Background", bgmenu); glutAddSubMenu("Display", displaymenu); glutAddMenuEntry("[L] Reload state", (int)'r'); glutAddMenuEntry("[Q] Quit", (int)'q'); glutAttachMenu(GLUT_RIGHT_BUTTON); /* Initialize variables */ RandomSeed = (int)time(NULL); Reset(); } /* InitGraphics() */ /**************************************************************** Keyboard */ static void Keyboard(unsigned char c, /*@unused@*/int u, /*@unused@*/int v) { int width, height; glutSetWindow(Window); switch( tolower(c) ) { /* Toggle options */ case 'o': case '0': Quality ^= 1; break; case '1': DisplayRule ^= 1; break; case '2': DisplayFractal ^= 1; break; case '3': DisplayBackground ^= 1; break; case 'p': Zoomed ^= 1; break; case '9': Density ^= 1; Rebuild = 1; break; /* Set orientation */ case '[': Orientate(1, 2, 0); break; case ']': Orientate(3, 0, 2); break; case '(': Orientate(1, 0, 2); break; case ')': Orientate(3, 2, 0); break; case '{': Orientate(0, 3, 1); break; case '}': Orientate(2, 1, 3); break; /* Set colors */ case 'r': FractalColor = FRACTAL_COLOR_R; break; case 'g': FractalColor = FRACTAL_COLOR_G; break; case 'b': FractalColor = FRACTAL_COLOR_B; break; case 'c': FractalColor = FRACTAL_COLOR_C; break; case 'm': FractalColor = FRACTAL_COLOR_M; break; case 'y': FractalColor = FRACTAL_COLOR_Y; break; case 'w': FractalColor = FRACTAL_COLOR_W; break; case 'k': FractalColor = FRACTAL_COLOR_K; break; /* Set invert mode */ case 'i': RenderInverted = 1; break; case 'n': RenderInverted = 0; break; /* Set blend mode */ case '4': AdditiveBlend = 1; break; case '5': AdditiveBlend = 0; break; /* Set background intensity */ case 'f': BGIntensity = 1.0f; break; case 'h': BGIntensity = 0.5f; break; case 't': BGIntensity = 0.3f; break; /* Set window size */ case '6': glutReshapeWindow(BGWidth, BGHeight); break; case '7': glutReshapeWindow(BGWidth * 2, BGHeight * 2); break; case '8': glutReshapeWindow(BGWidth * 4, BGHeight * 4); break; /* Adjust size to fit aspect ratio */ case 'x': width = glutGet(GLUT_WINDOW_WIDTH); height = width * BGHeight / BGWidth; glutReshapeWindow(width, height); break; case 'z': height = glutGet(GLUT_WINDOW_HEIGHT); width = height * BGWidth / BGHeight; glutReshapeWindow(width, height); break; /* Manipulate rules */ case 'a': /* Add rule */ AddRule(); break; case 'd': /* Delete selected rule */ RemoveRule(); break; case 'e': /* Delete all rules */ SaveUndo(Rule, RuleCount); RuleCount = 0; Selected = -1; Rebuild = 1; break; case '\t': /* Select next rule */ Selected++; if( Selected >= RuleCount ) Selected = 0; break; case 'u': /* Undo */ Undo(); break; /* Program control */ case 'l': /* Reload state */ Reset(); break; case 'q': /* Quit */ Quit(); break; default: /* Do nothing */ break; } glutPostRedisplay(); } /* Keyboard() */ /********************************************************** LoadBackground */ static void LoadBackground(char *name) { unsigned char *image, *origimage, *clut; unsigned short bits; char imgname[256]; FILE *infile; long imageoffset; int bps, colors, i, j, k, w, h; /* Open file */ strcpy(imgname, name); if( (infile = fopen(name, "rb")) == NULL ) { strcat(imgname, ".bmp"); if( (infile = fopen(imgname, "rb")) == NULL ) { printf("Can not open %s or %s.\n", name, imgname); return; } } printf("Loading %s...\r", imgname); /* Get dimensions */ (void)fseek(infile, BMP_WIDTH_OFFSET, SEEK_SET); (void)fread(&BGWidth, sizeof(int), 1, infile); (void)fread(&BGHeight, sizeof(int), 1, infile); if( BGWidth < 1 || BGWidth > MAX_BMP_WIDTH || BGHeight < 1 || BGHeight > MAX_BMP_HEIGHT ) { (void)fclose(infile); printf( "Size of %s is unsupported: (%d, %d)\n" "Images must be between (1, 1) and (%d, %d)\n", imgname, BGWidth, BGHeight, MAX_BMP_WIDTH, MAX_BMP_HEIGHT); return; } /* Allocate memory for image */ if( (image = (unsigned char*)malloc((size_t)(BGWidth * BGHeight * 4))) == NULL ) { (void)fclose(infile); printf("Not enough memory to load %s\n", imgname); return; } /* Get format */ (void)fseek(infile, BMP_BIT_OFFSET, SEEK_SET); (void)fread(&bits, sizeof(short), 1, infile); /* Load file to RGBA buffer */ if( bits == 8 ) { /* Paletted image */ /* Load color lookup table */ (void)fseek(infile, BMP_COLOR_OFFSET, SEEK_SET); (void)fread(&colors, sizeof(int), 1, infile); if( colors < 2 ) { (void)fclose(infile); free(image); printf("%s is of unsupported format.\n", imgname); return; } if( (clut = (unsigned char*)malloc((size_t)(colors * 3))) == NULL ) { (void)fclose(infile); free(image); printf("Not enough memory to load %s\n", imgname); return; } (void)fseek(infile, BMP_CLUT_OFFSET, SEEK_SET); for(i = 0; i < colors; i++) { clut[i * 3 + 2] = (unsigned char)fgetc(infile); clut[i * 3 + 1] = (unsigned char)fgetc(infile); clut[i * 3 ] = (unsigned char)fgetc(infile); (void)fgetc(infile); } /* Get bytes per scanline */ bps = (BGWidth + 3) & ~3; imageoffset = BMP_CLUT_OFFSET + colors * 4; /* Load image */ for(i = 0; i < BGHeight; i++) { (void)fseek(infile, (BGHeight - i - 1) * bps + imageoffset, SEEK_SET); for(j = 0; j < BGWidth; j++) { k = fgetc(infile); if( k >= colors ) continue; image[(i * BGWidth + j) * 4 ] = clut[k * 3]; image[(i * BGWidth + j) * 4 + 1] = clut[k * 3 + 1]; image[(i * BGWidth + j) * 4 + 2] = clut[k * 3 + 2]; image[(i * BGWidth + j) * 4 + 3] = (unsigned char)255; /* LCLint 2.5m complains that clut[] might be uninitialized. I guarantee you that it is, don't worry. */ } } free(clut); } else if( bits == 24 ) { /* True-color image */ /* Get bytes per scanline */ bps = ((BGWidth * 3) + 3) & ~3; imageoffset = BMP_CLUT_OFFSET; /* Load image */ for(i = 0; i < BGHeight; i++) { (void)fseek(infile, (BGHeight - i - 1) * bps + imageoffset, SEEK_SET); for(j = 0; j < BGWidth; j++) { image[(i * BGWidth + j) * 4 + 2] = (unsigned char)fgetc(infile); image[(i * BGWidth + j) * 4 + 1] = (unsigned char)fgetc(infile); image[(i * BGWidth + j) * 4 ] = (unsigned char)fgetc(infile); image[(i * BGWidth + j) * 4 + 3] = (unsigned char)255; } } } else { /* Unsupported format */ free(image); (void)fclose(infile); printf("Bit depth %d not supported, " "image must be 8bit or 24bit.\n", (int)bits); return; } (void)fclose(infile); /* Scale image if width or height is now power of 2 */ for(w = BGWidth; (w & -w) != w; w++); for(h = BGHeight; (h & -h) != h; h++); if( w != BGWidth || h != BGHeight ) { origimage = image; if( (image = (unsigned char*)malloc((size_t)(w * h * 4))) == NULL ) { free(origimage); printf("Not enough memory to scale %s\n", imgname); return; } (void)gluScaleImage( GL_RGBA, BGWidth, BGHeight, GL_UNSIGNED_BYTE, (GLvoid*)origimage, w, h, GL_UNSIGNED_BYTE, (GLvoid*)image); free(origimage); } /* Bind texture */ glGenTextures(1, &Background); glBindTexture(GL_TEXTURE_2D, Background); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)image); /* End */ free(image); printf("Background image %s loaded: %d, %d", imgname, BGWidth, BGHeight); if( BGWidth == w && BGHeight == h ) (void)putchar('\n'); else printf(" (scaled to %d, %d)\n", w, h); } /* LoadBackground() */ /************************************************************** LoadConfig */ static int LoadConfig(void) { FILE *infile; int i, options; if( (infile = fopen(ConfigFile, "rb")) == NULL ) return 0; (void)fread(&RuleCount, sizeof(int), 1, infile); (void)fread(&Selected, sizeof(int), 1, infile); if( RuleCount < 0 || RuleCount > MAX_IFS_RULES || Selected < -1 || Selected >= RuleCount ) { (void)fclose(infile); return 0; } (void)fread(&options, sizeof(int), 1, infile); FractalColor = options & OPT_FRACTAL_COLOR; if( FractalColor < 0 || FractalColor > FRACTAL_COLOR_K ) { (void)fclose(infile); return 0; } Quality = (options & OPT_QUALITY) != 0 ? 1 : 0; Density = (options & OPT_DENSITY) != 0 ? 1 : 0; DisplayRule = (options & OPT_DISPLAY_RULE) != 0 ? 1 : 0; DisplayFractal = (options & OPT_DISPLAY_FRACTAL) != 0 ? 1 : 0; AdditiveBlend = (options & OPT_ADDITIVE_MODE) != 0 ? 1 : 0; RenderInverted = (options & OPT_RENDER_INVERTED) != 0 ? 1 : 0; for(i = 0; i < RuleCount; i++) { (void)fread(&(Rule[i].x1), sizeof(GLdouble), 1, infile); (void)fread(&(Rule[i].y1), sizeof(GLdouble), 1, infile); (void)fread(&(Rule[i].x2), sizeof(GLdouble), 1, infile); (void)fread(&(Rule[i].y2), sizeof(GLdouble), 1, infile); (void)fread(&(Rule[i].x4), sizeof(GLdouble), 1, infile); (void)fread(&(Rule[i].y4), sizeof(GLdouble), 1, infile); if( Rule[i].x1 < MIN_PARAM_LIMIT || Rule[i].x1 > MAX_PARAM_LIMIT || Rule[i].x2 < MIN_PARAM_LIMIT || Rule[i].x2 > MAX_PARAM_LIMIT || Rule[i].x4 < MIN_PARAM_LIMIT || Rule[i].x4 > MAX_PARAM_LIMIT || Rule[i].y1 < MIN_PARAM_LIMIT || Rule[i].y1 > MAX_PARAM_LIMIT || Rule[i].y2 < MIN_PARAM_LIMIT || Rule[i].y2 > MAX_PARAM_LIMIT || Rule[i].y4 < MIN_PARAM_LIMIT || Rule[i].y4 > MAX_PARAM_LIMIT ) { (void)fclose(infile); return 0; } Rule[i].rebuild = 1; } Rebuild = 1; (void)fclose(infile); return 1; } /* LoadConfig() */ /******************************************************************** Menu */ static void Menu(int action) { /* Menu emulates keyboard */ Keyboard((unsigned char)action, 0, 0); } /* Menu() */ /************************************************************* MouseButton */ static void MouseButton(int button, int state, int x, int y) { static GLuint pickbuffer[PICK_BUFFER_SIZE]; int hitcount, lastselected, index, i; MouseX = x; MouseY = y; lastselected = Selected; if( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN ) { TmpCount = RuleCount; SaveState(TmpRule, Rule); Modified = 0; /* Button press */ glSelectBuffer(PICK_BUFFER_SIZE, pickbuffer); /* Try to select vertices first (two tries for broken boards) */ if( (hitcount = Pick(x, y, 0)) == 0 ) if( (hitcount = Pick(x, y, 0)) == 0 ) if( (hitcount = Pick(x, y, 1)) == 0 ) hitcount = Pick(x, y, 1); if( hitcount != 0 ) { if( Selected > -1 ) { /* Give priority to last selected */ index = 3; for(i = 0; i < hitcount; i++) { if( (int)(pickbuffer[index] & 0xff) == Selected ) break; index += 4; } if( i == hitcount ) index = 3; } else { index = 3; } MousePressed = 1; Selected = (int)pickbuffer[index]; Point = (int)((unsigned)Selected >> 8); Selected &= 255; } else { MousePressed = 0; Selected = -1; } } else { /* Button release */ MousePressed = 0; Point = 0; /* Save undo state */ if( Modified != 0 ) { Modified = 0; SaveUndo(TmpRule, TmpCount); } } glutSetWindow(Window); glutPostRedisplay(); } /* MouseButton() */ /************************************************************* MouseMotion */ static void MouseMotion(int x, int y) { int width, height, dx, dy; double u, v; dx = x - MouseX; dy = MouseY - y; MouseX = x; MouseY = y; if( MousePressed != 0 ) { /* Mouse drag detected, modify rule */ glutSetWindow(Window); width = glutGet(GLUT_WINDOW_WIDTH); y = (height = glutGet(GLUT_WINDOW_HEIGHT)) - y; if( x < 0 ) x = 0; if( x >= width ) x = width - 1; if( y < 0 ) y = 0; if( y >= height ) y = height - 1; if( Zoomed != 0 ) { u = ((double)(x * 2) / (double)width) - 0.5; v = ((double)(y * 2) / (double)height) - 0.5; } else { u = (double)x / (double)width; v = (double)y / (double)height; } switch( Point ) { case 1: Rule[Selected].x1 = u; Rule[Selected].y1 = v; break; case 2: Rule[Selected].x2 = u; Rule[Selected].y2 = v; break; case 4: Rule[Selected].x4 = u; Rule[Selected].y4 = v; break; case 3: Rule[Selected].x1 = Rule[Selected].x2 + Rule[Selected].x4 - u; Rule[Selected].y1 = Rule[Selected].y2 + Rule[Selected].y4 - v; break; default: if( Zoomed != 0 ) { u = ((double)(dx * 2) / (double)width); v = ((double)(dy * 2) / (double)height); } else { u = (double)dx / (double)width; v = (double)dy / (double)height; } Rule[Selected].x1 += u; Rule[Selected].y1 += v; Rule[Selected].x2 += u; Rule[Selected].y2 += v; Rule[Selected].x4 += u; Rule[Selected].y4 += v; break; } Rule[Selected].rebuild = Rebuild = Modified = 1; glutPostRedisplay(); } } /* MouseMotion() */ /*************************************************************** Orientate */ static void Orientate(int a, int b, int d) { GLdouble x[4], y[4]; if( Selected < 0 ) return; SaveUndo(Rule, RuleCount); x[2] = Rule[Selected].x3 = (x[1] = Rule[Selected].x2) + (x[3] = Rule[Selected].x4) - (x[0] = Rule[Selected].x1); y[2] = Rule[Selected].y3 = (y[1] = Rule[Selected].y2) + (y[3] = Rule[Selected].y4) - (y[0] = Rule[Selected].y1); Rule[Selected].x1 = x[a]; Rule[Selected].y1 = y[a]; Rule[Selected].x2 = x[b]; Rule[Selected].y2 = y[b]; Rule[Selected].x4 = x[d]; Rule[Selected].y4 = y[d]; Rule[Selected].rebuild = Rebuild = 1; } /* Orientate() */ /******************************************************************** Pick */ static int Pick(int x, int y, int o) { int width, height, viewport[4], i; /* Initialize picking */ (void)glRenderMode(GL_SELECT); glInitNames(); glPushName(0xffffffff); /* Initialize window */ glDrawBuffer(GL_BACK); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); /* Initialize viewport */ viewport[0] = viewport[1] = 0; viewport[2] = width = glutGet(GLUT_WINDOW_WIDTH); viewport[3] = height = glutGet(GLUT_WINDOW_HEIGHT); glViewport(0, 0, width, height); /* Initialize transformations */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix((GLdouble)x, (GLdouble)(height - y), PICK_TOLERANCE, PICK_TOLERANCE, viewport); if( Zoomed != 0 ) gluOrtho2D(-0.5, 1.5, -0.5, 1.5); else gluOrtho2D(0.0, 1.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw object to be picked */ if( o != 0 ) { for(i = 0; i < RuleCount; i++) { glLoadName((GLuint)i); glBegin(GL_POLYGON); glVertex3d(Rule[i].x1, Rule[i].y1, 0.0); glVertex3d(Rule[i].x2, Rule[i].y2, 0.0); glVertex3d(Rule[i].x4, Rule[i].y4, 0.0); glEnd(); } } else { for(i = 0; i < RuleCount; i++) { glPointSize((GLfloat)PICK_TOLERANCE); glLoadName((GLuint)((1 << 8) | i)); glBegin(GL_POINTS); glVertex3d(Rule[i].x1, Rule[i].y1, 0.0); glEnd(); glLoadName((GLuint)((2 << 8) | i)); glBegin(GL_POINTS); glVertex3d(Rule[i].x2, Rule[i].y2, 0.0); glEnd(); glLoadName((GLuint)((3 << 8) | i)); glBegin(GL_POINTS); glVertex3d(Rule[i].x3, Rule[i].y3, 0.0); glEnd(); glLoadName((GLuint)((4 << 8) | i)); glBegin(GL_POINTS); glVertex3d(Rule[i].x4, Rule[i].y4, 0.0); glEnd(); } } /* End */ glFlush(); return glRenderMode(GL_RENDER); } /* Pick() */ /******************************************************************** Quit */ static void Quit(void) { int i; /* Free display lists */ for(i = 0; i < MAX_IFS_RULES; i++) glDeleteLists(Rule[i].list, 1); glDeleteLists(Fractal, 1); /* Free texture */ if( Background != 0 ) glDeleteTextures(1, &Background); /* Write configuration to disk */ SaveConfig(); /* Write PostScript file */ SavePostScript(); if( RuleCount != 0) { /* Write configuration to stdout */ (void)puts( "x = a * x0 + b * y0 + e\ny = c * x0 + d * y0 + f\n\n" " a b c d e f"); if( Rebuild != 0 ) { for(i = 0; i < RuleCount; i++) { Rule[i].e = Rule[i].x1; Rule[i].f = Rule[i].y1; Rule[i].a = Rule[i].x2 - Rule[i].e; Rule[i].b = Rule[i].x4 - Rule[i].e; Rule[i].c = Rule[i].y2 - Rule[i].f; Rule[i].d = Rule[i].y4 - Rule[i].f; } } for(i = 0; i < RuleCount; i++) { printf("%2d: %+.4f, %+.4f, %+.4f, %+.4f, %+.4f, %+.4f\n", i + 1, Rule[i].a, Rule[i].b, Rule[i].c, Rule[i].d, Rule[i].e, Rule[i].f); } } glFinish(); glutDestroyWindow(Window); free(ConfigFile); free(PSFile); exit(EXIT_SUCCESS); } /* Quit() */ /************************************************************** RemoveRule */ static void RemoveRule(void) { int i; /* Return if nothing selected to be deleted */ if( Selected < 0 ) return; /* Save undo state */ SaveUndo(Rule, RuleCount); /* Shift rules */ RuleCount--; for(i = Selected; i < RuleCount; i++) { Rule[i].x1 = Rule[i + 1].x1; Rule[i].x2 = Rule[i + 1].x2; Rule[i].x4 = Rule[i + 1].x4; Rule[i].y1 = Rule[i + 1].y1; Rule[i].y2 = Rule[i + 1].y2; Rule[i].y4 = Rule[i + 1].y4; Rule[i].rebuild = 1; } /* Notify Display() to update display lists */ Rebuild = 1; Selected = -1; } /* RemoveRule() */ /******************************************************************* Reset */ static void Reset(void) { Point = 0; MousePressed = 0; MouseX = MouseY = 0; UndoStackIndex = UndoStackStart = 0; Modified = 0; Zoomed = 0; DisplayBackground = 1; BGIntensity = 0.5f; if( LoadConfig() == 0 ) { Selected = -1; Quality = Density = 0; AdditiveBlend = 1; RenderInverted = 0; DisplayRule = DisplayFractal = 1; FractalColor = FRACTAL_COLOR_G; RuleCount = 0; AddRule(); AddRule(); SaveConfig(); } } /* Reset() */ /***************************************************************** Reshape */ static void Reshape(/*@unused@*/int w, /*@unused@*/int h) { glutSetWindow(Window); glutPostRedisplay(); } /* Reshape() */ /************************************************************** SaveConfig */ static void SaveConfig(void) { FILE *outfile; int i, options; if( SaveOnExit == 0 ) return; options = FractalColor; if( Quality != 0 ) options |= OPT_QUALITY; if( Density != 0 ) options |= OPT_DENSITY; if( DisplayRule != 0 ) options |= OPT_DISPLAY_RULE; if( DisplayFractal != 0 ) options |= OPT_DISPLAY_FRACTAL; if( AdditiveBlend != 0 ) options |= OPT_ADDITIVE_MODE; if( RenderInverted != 0 ) options |= OPT_RENDER_INVERTED; if( (outfile = fopen(ConfigFile, "wb+")) == NULL ) return; (void)fwrite(&RuleCount, sizeof(int), 1, outfile); (void)fwrite(&Selected, sizeof(int), 1, outfile); (void)fwrite(&options, sizeof(int), 1, outfile); for(i = 0; i < RuleCount; i++) { (void)fwrite(&(Rule[i].x1), sizeof(GLdouble), 1, outfile); (void)fwrite(&(Rule[i].y1), sizeof(GLdouble), 1, outfile); (void)fwrite(&(Rule[i].x2), sizeof(GLdouble), 1, outfile); (void)fwrite(&(Rule[i].y2), sizeof(GLdouble), 1, outfile); (void)fwrite(&(Rule[i].x4), sizeof(GLdouble), 1, outfile); (void)fwrite(&(Rule[i].y4), sizeof(GLdouble), 1, outfile); } (void)fclose(outfile); } /* SaveConfig() */ /*************************************************************** SaveState */ static void SaveState(IFSRule dst[], IFSRule src[]) { int i; for(i = 0; i < MAX_IFS_RULES; i++) memcpy(&dst[i], &src[i], sizeof(IFSRule)); } /* SaveState() */ /********************************************************** SavePostScript */ static void SavePostScript(void) { FILE *outfile; int i; if( SaveOnExit == 0 ) return; if( (outfile = fopen(PSFile, "wt+")) == NULL ) return; /* Header */ fprintf(outfile, "%%!PS-Adobe-2.0\n" "%%%%BoundingBox: 0 0 612 792\n" "%%%%Pages: 1\n" "%%%%EndComments\n" "%%%%Page: 1 1\n\n" "%% page size\n" "/MINX %d def /MINY %d def\n" "/MAXX %d def /MAXY %d def\n\n", PS_MINX, PS_MINY, PS_MAXX, PS_MAXY); /* Data */ (void)fputs( "% fractal data\n" "/r\n" " % b e a f c d\n", outfile); for(i = 0; i < RuleCount; i++) { fprintf(outfile, "%c [%+.4f %+.4f %+.4f %+.4f %+.4f %+.4f]%s", i == 0 ? '[' : ' ', Rule[i].b, Rule[i].e, Rule[i].a, Rule[i].f, Rule[i].c, Rule[i].d, i + 1 == RuleCount ? " ] def\n\n" : "\n"); } /* Draw point */ fprintf(outfile, "%% renderer\n" "/d\n{\n" "\t2 copy\n" "\tMAXY MINY sub mul MINY add exch\n" "\tMAXX MINX sub mul MINX add exch\n" "\tnewpath\n" "\t%d add moveto\n" "\t%d %d rlineto\n" "\t%d %d rlineto\n" "\t%d %d rlineto\n" "\tclosepath fill\n" "} def\n", PS_POINT_SIZE, PS_POINT_SIZE, -PS_POINT_SIZE, -PS_POINT_SIZE, -PS_POINT_SIZE, -PS_POINT_SIZE, PS_POINT_SIZE); /* Move point */ fprintf(outfile, "/i\n{\n" "\tr rand %d mod get aload pop\n" "\t6 index mul exch 7 index mul add add\n" "\t6 1 roll 5 4 roll\n" "\tmul 4 2 roll mul add add\n" "\texch\n" "} def\n", RuleCount); /* Draw image */ fprintf(outfile, "/Render\n{\n" "\terasepage\n" "\t%d {\n" "\t\trand 4095 and 4095 div\n" "\t\trand 4095 and 4095 div\n" "\t\t%d {i} repeat\n" "\t\t%d {i d} repeat\n" "\t\tpop pop\n" "\t} repeat\n" "} def\n\nrealtime srand\nRender\nshowpage\n", DOT_COUNT + Density * DOT_COUNT, INIT_ITER_COUNT, ITERATION_COUNT + Density * ITERATION_COUNT); (void)fclose(outfile); } /* SavePostScript() */ /**************************************************************** SaveUndo */ static void SaveUndo(IFSRule source[], int count) { int index; if( UndoStackIndex >= IFS_STACK_SIZE ) { SaveState(UndoStack[UndoStackStart], source); UndoCount[UndoStackStart++] = count; if( UndoStackStart >= IFS_STACK_SIZE ) UndoStackStart = 0; } else { index = (UndoStackStart + UndoStackIndex) % IFS_STACK_SIZE; SaveState(UndoStack[index], source); UndoCount[index] = count; UndoStackIndex++; } } /* SaveUndo() */ /******************************************************************** Undo */ static void Undo(void) { int i; if( UndoStackIndex == 0 ) return; UndoStackIndex--; i = (UndoStackStart + UndoStackIndex) % IFS_STACK_SIZE; SaveState(Rule, UndoStack[i]); RuleCount = UndoCount[i]; for(i = 0; i < MAX_IFS_RULES; Rule[i++].rebuild = 1); Rebuild = 1; } /* Undo() */