/* 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 Last minute features: -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) */ /* 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 256 #define ITERATION_COUNT 256 #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_FRACTAL_COLOR 15 enum { FRACTAL_COLOR_R, 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, COLOR_COUNT }; /* Types */ typedef struct { GLdouble x1, y1, x2, y2, x3, y3; GLdouble a, b, c, d, e, f, x4, y4; GLuint list; int rebuild; } IFSRule; /* Globals */ static char ConfigFile[256], PSFile[256]; static int SaveOnExit; static char *BGFile; static int DisplayBackground; static int BGWidth, BGHeight; static GLuint Background; static GLfloat BGIntensity; static int Window; static int MousePressed; static int MouseX, MouseY; static int Selected, Point; static int Quality; 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[COLOR_COUNT][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}, /* INV_GUIDE_COLOR */ {1.0f, 1.0f, 1.0f, 0.5f}, /* INV_OUTLINE_COLOR */ {0.0f, 1.0f, 0.0f, 0.9f}, /* SELECTED_COLOR */ {1.0f, 0.0f, 0.0f, 0.9f} }; /* MODIFY_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, int u, 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 int Pick(int x, int y, int o); static void Quit(void); static void RemoveRule(void); static void Reset(void); static void Reshape(int w, 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) { int i; puts("IFS 1.1 (7/17/01) - Don Yang (http://uguu.org)\n"); /* Initialize OpenGL */ glutInit(&argc, argv); /* Parse command line */ strcpy(ConfigFile, DEFAULT_CONFIG_FILE); Background = 0; BGFile = NULL; SaveOnExit = 1; for(i = 1; i < argc; i++) { if( argv[i][0] == '-' ) { if( i + 1 < argc ) { if( tolower(argv[i][1]) == 'c' ) { strcpy(ConfigFile, argv[++i]); if( !strchr(ConfigFile, '.') ) strcat(ConfigFile, ".cfg"); continue; } else if( tolower(argv[i][1]) == 'b' ) { BGFile = argv[++i]; continue; } } else if( tolower(argv[i][1]) == 'n' ) { SaveOnExit = 0; continue; } } /* Command line help */ 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; } 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, x3, y3; if( RuleCount >= MAX_IFS_RULES ) return; /* Save undo state */ SaveUndo(Rule, RuleCount); /* Generate random rule */ srand((unsigned)RandomSeed); x1 = rand(); x2 = rand(); x3 = rand(); y1 = rand(); y2 = rand(); y3 = 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].x3 = (x3 & 2047) / 2047.0; Rule[RuleCount].y3 = (y3 & 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 ) 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 ) { 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(); gluOrtho2D(0.0, 1.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw background image */ if( Background && DisplayBackground ) { 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); glTexEnvf(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 ) { glBlendFunc(GL_SRC_ALPHA, AdditiveBlend ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA); glColor4fv(Palette[FractalColor]); if( Rebuild ) { glNewList(Fractal, GL_COMPILE_AND_EXECUTE); DrawFractals(); glEndList(); Rebuild = 0; } else { glCallList(Fractal); } } /* Draw rules */ if( DisplayRule ) { /* Draw outline */ glColor4fv(Palette[RenderInverted ? OUTLINE_COLOR_I : OUTLINE_COLOR]); glBlendFunc(GL_SRC_COLOR, GL_ZERO); glLineWidth(5.0f); for(i = 0; i < RuleCount; i++) { if( Rule[i].rebuild ) { 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 ) { if( Point ) glColor4fv(Palette[MODIFY_COLOR]); else glColor4fv(Palette[SELECTED_COLOR]); } else { glColor4fv(Palette[RenderInverted ? 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 ) 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].x3 - Rule[i].e; Rule[i].c = Rule[i].y2 - Rule[i].f; Rule[i].d = Rule[i].y3 - Rule[i].f; } /* Trace dots (using fixed random number seed) */ srand(FIXED_RAND_SEED); glBegin(GL_POINTS); for(i = 0; i < DOT_COUNT; 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; 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].x4 = Rule[index].x2 + Rule[index].x3 - Rule[index].x1; Rule[index].y4 = Rule[index].y2 + Rule[index].y3 - 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].x3, Rule[index].y3, 0.0); glEnd(); glBegin(GL_LINE_STRIP); glVertex3d( Rule[index].x4 - 0.3 * (Rule[index].x4 - Rule[index].x3), Rule[index].y4 - 0.3 * (Rule[index].y4 - Rule[index].y3), 0.0); glVertex3d(Rule[index].x4, Rule[index].y4, 0.0); glVertex3d( Rule[index].x4 - 0.1 * (Rule[index].x4 - Rule[index].x2), Rule[index].y4 - 0.1 * (Rule[index].y4 - Rule[index].y2), 0.0); glEnd(); } /* DrawRule() */ /************************************************************ InitGraphics */ static void InitGraphics(void) { int rulemenu, fractalmenu, displaymenu, bgmenu = 0; int i; /* Initialize window */ glutInitDisplayMode(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 ) { LoadBackground(BGFile); if( Background ) { 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'); 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 ) { bgmenu = glutCreateMenu(Menu); glutAddMenuEntry("[F] Full intensity", (int)'f'); glutAddMenuEntry("[H] Half intensity", (int)'h'); glutAddMenuEntry("[T] 1/3 intensity", (int)'t'); glutAddMenuEntry("[9] Set window to background size", (int)'9'); glutAddMenuEntry("[8] Set window to 2x background size", (int)'8'); glutAddMenuEntry("[7] Set window to 4x background size", (int)'7'); 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 ) glutAddMenuEntry("[3] Toggle background display", (int)'3'); glutAddMenuEntry("[0] Toggle quality", (int)'0'); glutCreateMenu(Menu); glutAddSubMenu("Rules", rulemenu); glutAddSubMenu("Fractals", fractalmenu); if( Background ) 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, int u, int v) { int width, height; glutSetWindow(Window); switch( tolower(c) ) { /* Toggle options */ case 'o': case '0': Quality = !Quality; break; case '1': DisplayRule = !DisplayRule; break; case '2': DisplayFractal = !DisplayFractal; break; case '3': DisplayBackground = !DisplayBackground; 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 '9': glutReshapeWindow(BGWidth, BGHeight); break; case '8': glutReshapeWindow(BGWidth * 2, BGHeight * 2); break; case '7': 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 */ fseek(infile, BMP_WIDTH_OFFSET, SEEK_SET); fread(&BGWidth, sizeof(int), 1, infile); fread(&BGHeight, sizeof(int), 1, infile); if( BGWidth < 1 || BGWidth > MAX_BMP_WIDTH || BGHeight < 1 || BGHeight > MAX_BMP_HEIGHT ) { 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 ) { fclose(infile); printf("Not enough memory to load %s\n", imgname); return; } /* Get format */ fseek(infile, BMP_BIT_OFFSET, SEEK_SET); fread(&bits, sizeof(short), 1, infile); /* Load file to RGBA buffer */ if( bits == 8 ) { /* Paletted image */ /* Load color lookup table */ fseek(infile, BMP_COLOR_OFFSET, SEEK_SET); fread(&colors, sizeof(int), 1, infile); if( colors < 2 ) { fclose(infile); free(image); printf("%s is of unsupported format.\n", imgname); return; } if( (clut = (unsigned char *)malloc((size_t)(colors * 3))) == NULL ) { fclose(infile); free(image); printf("Not enough memory to load %s\n", imgname); return; } 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); fgetc(infile); } /* Get bytes per scanline */ for(bps = BGWidth; bps % 4; bps++); imageoffset = BMP_CLUT_OFFSET + colors * 4; /* Load image */ for(i = 0; i < BGHeight; i++) { 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 */ for(bps = BGWidth * 3; bps % 4; bps++); imageoffset = BMP_CLUT_OFFSET; /* Load image */ for(i = 0; i < BGHeight; i++) { 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); fclose(infile); printf("Bit depth %d not supported, " "image must be 8bit or 24bit.\n", (int)bits); return; } 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; } 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 ) 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; fread(&RuleCount, sizeof(int), 1, infile); fread(&Selected, sizeof(int), 1, infile); if( RuleCount < 0 || RuleCount > MAX_IFS_RULES || Selected < -1 || Selected >= RuleCount ) { fclose(infile); return 0; } fread(&options, sizeof(int), 1, infile); FractalColor = options & OPT_FRACTAL_COLOR; if( FractalColor < 0 || FractalColor > FRACTAL_COLOR_K ) { fclose(infile); return 0; } Quality = (options & OPT_QUALITY) ? 1 : 0; DisplayRule = (options & OPT_DISPLAY_RULE) ? 1 : 0; DisplayFractal = (options & OPT_DISPLAY_FRACTAL) ? 1 : 0; AdditiveBlend = (options & OPT_ADDITIVE_MODE) ? 1 : 0; RenderInverted = (options & OPT_RENDER_INVERTED) ? 1 : 0; for(i = 0; i < RuleCount; i++) { fread(&(Rule[i].x1), sizeof(GLdouble), 1, infile); fread(&(Rule[i].y1), sizeof(GLdouble), 1, infile); fread(&(Rule[i].x2), sizeof(GLdouble), 1, infile); fread(&(Rule[i].y2), sizeof(GLdouble), 1, infile); fread(&(Rule[i].x3), sizeof(GLdouble), 1, infile); fread(&(Rule[i].y3), 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].x3 < MIN_PARAM_LIMIT || Rule[i].x3 > 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].y3 < MIN_PARAM_LIMIT || Rule[i].y3 > MAX_PARAM_LIMIT ) { fclose(infile); return 0; } Rule[i].rebuild = 1; } Rebuild = 1; 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)) ) if( !(hitcount = Pick(x, y, 0)) ) if( !(hitcount = Pick(x, y, 1)) ) /* Try entire rule if no hit */ hitcount = Pick(x, y, 1); if( hitcount ) { 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)(Selected >> 8); Selected &= 255; } else { MousePressed = 0; Selected = -1; } } else { /* Button release */ MousePressed = 0; Point = 0; /* Save undo state */ if( Modified ) { 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 ) { /* 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( Point == 1 ) { Rule[Selected].x1 = (double)x / (double)width; Rule[Selected].y1 = (double)y / (double)height; } else if( Point == 2 ) { Rule[Selected].x2 = (double)x / (double)width; Rule[Selected].y2 = (double)y / (double)height; } else if( Point == 3 ) { Rule[Selected].x3 = (double)x / (double)width; Rule[Selected].y3 = (double)y / (double)height; } else { Rule[Selected].x1 += (u = (double)dx / (double)width); Rule[Selected].y1 += (v = (double)dy / (double)height); Rule[Selected].x2 += u; Rule[Selected].y2 += v; Rule[Selected].x3 += u; Rule[Selected].y3 += v; } Rule[Selected].rebuild = Rebuild = Modified = 1; glutPostRedisplay(); } } /* MouseMotion() */ /******************************************************************** Pick */ static int Pick(int x, int y, int o) { int width, height, viewport[4], i; /* Initialize picking */ 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); gluOrtho2D(0.0, 1.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw object to be picked */ if( o ) { 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].x3, Rule[i].y3, 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(); } } /* 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 ) glDeleteTextures(1, &Background); /* Write configuration to disk */ SaveConfig(); /* Write PostScript file */ SavePostScript(); if( RuleCount ) { /* Write configuration to stdout */ puts( "x = a * x0 + b * y0 + e\ny = c * x0 + d * y0 + f\n\n" " a b c d e f"); if( Rebuild ) { 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].x3 - Rule[i].e; Rule[i].c = Rule[i].y2 - Rule[i].f; Rule[i].d = Rule[i].y3 - 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); 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].x3 = Rule[i + 1].x3; Rule[i].y1 = Rule[i + 1].y1; Rule[i].y2 = Rule[i + 1].y2; Rule[i].y3 = Rule[i + 1].y3; 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; DisplayBackground = 1; BGIntensity = 0.5f; if( !LoadConfig() ) { Selected = -1; Quality = 0; AdditiveBlend = 1; RenderInverted = 0; DisplayRule = DisplayFractal = 1; FractalColor = FRACTAL_COLOR_G; RuleCount = 0; AddRule(); AddRule(); SaveConfig(); } } /* Reset() */ /***************************************************************** Reshape */ static void Reshape(int w, int h) { glutSetWindow(Window); glutPostRedisplay(); } /* Reshape() */ /************************************************************** SaveConfig */ static void SaveConfig(void) { FILE *outfile; int i, options; if( !SaveOnExit ) return; options = FractalColor; if( Quality ) options |= OPT_QUALITY; if( DisplayRule ) options |= OPT_DISPLAY_RULE; if( DisplayFractal ) options |= OPT_DISPLAY_FRACTAL; if( AdditiveBlend ) options |= OPT_ADDITIVE_MODE; if( RenderInverted ) options |= OPT_RENDER_INVERTED; if( (outfile = fopen(ConfigFile, "wb+")) == NULL ) return; fwrite(&RuleCount, sizeof(int), 1, outfile); fwrite(&Selected, sizeof(int), 1, outfile); fwrite(&options, sizeof(int), 1, outfile); for(i = 0; i < RuleCount; i++) { fwrite(&(Rule[i].x1), sizeof(GLdouble), 1, outfile); fwrite(&(Rule[i].y1), sizeof(GLdouble), 1, outfile); fwrite(&(Rule[i].x2), sizeof(GLdouble), 1, outfile); fwrite(&(Rule[i].y2), sizeof(GLdouble), 1, outfile); fwrite(&(Rule[i].x3), sizeof(GLdouble), 1, outfile); fwrite(&(Rule[i].y3), sizeof(GLdouble), 1, outfile); } 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 ) 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 */ 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, INIT_ITER_COUNT, ITERATION_COUNT); 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 ) 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() */