/***************************************************************************** FOREST: This is a fancy 3D version of the forest screen saver that comes with xlock except that this can rotate the scene, clip the trees, move the viewpoint etc. Written by Neil Robertson, initial version in June 2005. Version: 20050719 Compiling: CC/c++ -L/usr/X11R6/lib -lX11 -lXext -lm Keys: R - Recreate new scene Q - Quit A - Decrease perspective half distance Z - Increase above S - Increase scaling X - Decrease scaling F - Move Z clip into screen V - Move Z clip out of screen U - Rotate anticlockwise about Y axis I - Stop rotating O - Rotate clockwise L - Toggle clipping Y - Toggle perspective Arrow up - Move Z viewpoint into screen Arrow down - Move Z viewpoint out of screen Arrow left - Move left Arrow right - Move right Page up - Move up Page down - Move down Mouse: Button 1 - Rotate scene *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 300 #define HEIGHT 300 #define HD_INC 20 #define X_VP_INC 5 #define Z_VP_INC 20 #define CLIP_INC 5 #define SCALE_INC 0.01 #define DEPTH 1000 #define THICKNESS 2 #define NUM_TREES 5 #define MAX_BRANCHES 5 #define MIN_BRANCHES 2 #define MIN_LEVELS 2 #define MAX_LEVELS 4 #define MIN_LEN 20 #define MAX_LEN 100 #define MAX_BRANCH_Z_ANG 90 #define MIN_BRANCH_Z_ANG 20 #define CONT_TRUNK_PROB 70 #define LEN_SHRINK 0.8 #define NUM_COLOURS 12 #define SWAY_DIV 2 #define DELAY 10000 #define DEGS_PER_RADIAN 57.29578 Display *display; char *disp; Window win; XdbeSwapInfo swapinfo; Drawable drw; GC gcb,gcol[NUM_COLOURS]; int width; int height; int width2; int height2; int num_trees; int num_branches; int draw_cnt; int max_levels; int min_levels; int max_len; int min_len; int max_branches; int min_branches; int max_branch_z_ang; int min_branch_z_ang; int cont_trunk_prob; int delay; int x_viewpoint; int y_viewpoint; int z_viewpoint; int ptr_x; int ptr_y; int last_ptr_x; int last_ptr_y; int half_dist; int depth; int y_rot_ang; int x_rot_ang; int y_ang_add; int z_clippoint; int sway_ang; int sway_div; double len_shrink; double x_start_ang; double scale; double thickness; bool do_col; bool do_pers; bool do_clip; bool use_dbe; bool show_info; double sinang[360]; double cosang[360]; class cl_branch; void parse_cmd_line(int argc, char **argv); void X_init(); void init(); void mainloop(); void reset(); void z_sort(); void set_win_vars(); void set_rotation_angles(); void rotate_point( double &x, double &y, double &z, int x_rot, int y_rot, int z_rot); double get_pmult(double z); int get_random(int min, int max); /******************************* Branch class *******************************/ class cl_branch { public: GC gc; int level; int num_levels; int orig_z_ang; // Angle before sway added int dest_z_ang; // Angle we're heading for with sway double tmp_z_ang; // Can be negative , used to set z_ang int z_ang; // Angle rotate around z axis of parent int y_ang; // Angle rotate around y axis of parent int num_children; int avg_z_ang; int avg_x_ang; int xpos; int zpos; double len; double x1,y1,z1; double x2,y2,z2; double zavg; double thick; int dx1,dy1; int dx2,dy2; cl_branch *parent; cl_branch *trunk; cl_branch **child; cl_branch(cl_branch *par, int ya, int za); ~cl_branch(); void create_trunk_and_branches(); void calculate(); void draw(bool do_draw); }; cl_branch **tree; cl_branch **branch; /*** Constructor ***/ cl_branch::cl_branch(cl_branch *par, int ya, int za) { double cpos; int index; if (par) { parent = par; len = parent->len * len_shrink; num_levels = parent->num_levels; level = parent->level + 1; xpos = parent->xpos; zpos = parent->zpos; } else { parent = NULL; num_levels = get_random(min_levels,max_levels); len = (double)get_random(min_len,max_len); level = 1; xpos = (random() % width) - width2; zpos = (random() % depth) - (depth/2); } trunk = NULL; cpos = (NUM_COLOURS-1) / (double)(num_levels - 1); index = (int)cpos * (level - 1); gc = gcol[index]; orig_z_ang = dest_z_ang = tmp_z_ang = z_ang = za; y_ang = ya; if (level < num_levels) create_trunk_and_branches(); else num_children = 0; num_branches++; } /*** Recursive destructor ***/ cl_branch::~cl_branch() { int i; if (trunk) delete trunk; if (num_children) { for(i=0;i < num_children;++i) delete child[i]; delete[] child; } } /*** Create branches off of a trunk element ***/ void cl_branch::create_trunk_and_branches() { int xang,zang,angadd; int i; if (random() % 100 < cont_trunk_prob) trunk = new cl_branch(this,0,0); num_children = get_random(min_branches,max_branches); angadd = 360 / num_children; xang = random() % 360; zang = get_random(min_branch_z_ang,max_branch_z_ang); if (num_children) { child = new cl_branch*[num_children]; for(i=0;i < num_children;++i,xang = (xang + angadd) % 360) child[i] = new cl_branch(this,xang,zang); } } /*** Do all the 3D stuff ***/ void cl_branch::calculate() { cl_branch *br; double pmult; double ratio; int sang; int i; // Add sway. if (sway_ang) { if ((int)tmp_z_ang == dest_z_ang) { sang = (int)((double)sway_ang / (num_levels - (level - 1))); dest_z_ang = orig_z_ang + (random() % ((sang * 2) + 1) - sang); } else { // tmp_z_ang can go negative , whereas z_ang cannot tmp_z_ang += ((double)(tmp_z_ang < dest_z_ang) / sway_div - (double)(tmp_z_ang > dest_z_ang) / sway_div); z_ang = ((int)tmp_z_ang % 360); if (z_ang < 0) z_ang += 360; } } // First rotate by our own angles x1 = y1 = z1 = 0; x2 = z2 = 0; y2 = len; rotate_point(x2,y2,z2,0,y_ang,z_ang); // Rotate by angles in heirarchy for(br=this->parent;br;br=br->parent) { y1 += br->len; y2 += br->len; rotate_point(x1,y1,z1,0,br->y_ang,br->z_ang); rotate_point(x2,y2,z2,0,br->y_ang,br->z_ang); } x1 += xpos; x2 += xpos; z1 += (zpos + z_viewpoint); z2 += (zpos + z_viewpoint); // Rotate by final viewpoint rotate_point(x1,y1,z1,x_rot_ang,y_rot_ang,0); rotate_point(x2,y2,z2,x_rot_ang,y_rot_ang,0); // Call all child branches to do the same for(i=0;i < num_children;++i) child[i]->calculate(); // Call the trunk if (trunk) trunk->calculate(); // Add perspective and scale. Take height2 from y1 & y2 so perspective // is along centre horizontal line of window, not the bottom pmult = get_pmult(z1); x1 = (x1 + x_viewpoint) * scale * pmult + width2; y1 = height - ((y1 + y_viewpoint - height2) * scale * pmult + height2); pmult = get_pmult(z2); x2 = (x2 + x_viewpoint) * scale * pmult + width2; y2 = height - ((y2 + y_viewpoint - height2) * scale * pmult + height2); // Clip if (do_clip) { if (z1 < z_clippoint) { if (z2 < z_clippoint) return; ratio = (z2 - z_clippoint) / (z2 - z1); x1 = x2 + (x1 - x2) * ratio; y1 = y2 + (y1 - y2) * ratio; } else if (z2 < z_clippoint) { if (z1 < z_clippoint) return; ratio = (z1 - z_clippoint) / (z1 - z2); x2 = x1 + (x2 - x1) * ratio; y2 = y1 + (y2 - y1) * ratio; } } branch[draw_cnt++] = this; } /*** Draw a branch. This is non recursive since its called after a Z sort on all branches of all trees ***/ void cl_branch::draw(bool do_draw) { GC gcd; if (do_draw) { thick = -1; gcd = gc; // Change line thickness depending on distance away. Some x servers // won't undraw the line properly if thickness is < 2 zavg = (z1 + z2) / 2; thick = thickness * scale * get_pmult(zavg); if (thick < 2) thick = 2; XSetLineAttributes(display,gcd,(int)thick,LineSolid,CapRound,JoinRound); // Set drawing positions dx1 = (int)x1; dx2 = (int)x2; dy1 = (int)y1; dy2 = (int)y2; } else { if (thick == -1) return; gcd = gcb; XSetLineAttributes(display,gcd,(int)thick,LineSolid,CapRound,JoinRound); } XDrawLine(display,drw,gcd,dx1,dy1,dx2,dy2); } /******************************* Start here ********************************/ main(int argc, char **argv) { parse_cmd_line(argc,argv); X_init(); init(); mainloop(); } /*** Parse the command line ***/ void parse_cmd_line(int argc, char **argv) { const char *opt[] = { "d", "w", "h", "depth", "trees", "tprob", "maxlev", "minlev", "maxlen", "minlen", "maxbr", "minbr", "maxbza", "minbza", "shrink", "thick", "sway", "swaydiv", "pers", "clip", "dbe", "info", "delay", "help" }; enum { OPT_D, OPT_W, OPT_H, OPT_DEPTH, OPT_TREES, OPT_TPROB, OPT_MAXLEV, OPT_MINLEV, OPT_MAXLEN, OPT_MINLEN, OPT_MAXBR, OPT_MINBR, OPT_MAXBZA, OPT_MINBZA, OPT_SHRINK, OPT_THICK, OPT_SWAY, OPT_SWAYDIV, OPT_PERS, OPT_CLIP, OPT_DBE, OPT_INFO, OPT_DELAY, OPT_HELP, OPT_END }; int i,o; double val; disp = NULL; width = WIDTH; height = HEIGHT; depth = DEPTH; num_trees = NUM_TREES; max_levels = MAX_LEVELS; min_levels = MIN_LEVELS; max_len = MAX_LEN; min_len = MIN_LEN; max_branches = MAX_BRANCHES; min_branches = MIN_BRANCHES; max_branch_z_ang = MAX_BRANCH_Z_ANG; min_branch_z_ang = MIN_BRANCH_Z_ANG; cont_trunk_prob = CONT_TRUNK_PROB; len_shrink = LEN_SHRINK; thickness = THICKNESS; sway_ang = 0; sway_div = SWAY_DIV; delay = DELAY; do_pers = false; do_clip = false; use_dbe = false; show_info = false; for(i=1;i < argc;i+=2) { if (argv[i][0] != '-') goto USAGE; for(o=0;o != OPT_END;++o) if (!strcasecmp(opt[o],argv[i]+1)) break; switch(o) { case OPT_DBE: case OPT_PERS: case OPT_CLIP: case OPT_INFO: case OPT_HELP: --i; break; default: if (i == argc-1) goto USAGE; val = atof(argv[i+1]); } switch(o) { case OPT_D: disp = argv[i+1]; break; case OPT_W: if (val < 1) goto USAGE; width = (int)val; break; case OPT_H: if (val < 1) goto USAGE; height = (int)val; break; case OPT_DEPTH: if (val < 1) goto USAGE; depth = (int)val; break; case OPT_TREES: if (val < 1) goto USAGE; num_trees = (int)val; break; case OPT_TPROB: if (val < 0 || val > 100) goto USAGE; cont_trunk_prob = (int)val; break; case OPT_MAXLEV: if (val < 1) goto USAGE; max_levels = (int)val; break; case OPT_MINLEV: if (val < 1) goto USAGE; min_levels = (int)val; break; case OPT_MAXLEN: if (val < 1) goto USAGE; max_len = (int)val; break; case OPT_MINLEN: if (val < 1) goto USAGE; min_len = (int)val; break; case OPT_MAXBR: if (val < 0) goto USAGE; max_branches = (int)val; break; case OPT_MINBR: if (val < 0) goto USAGE; min_branches = (int)val; break; case OPT_MAXBZA: if (val < 0 || val > 359) goto USAGE; max_branch_z_ang = (int)val; break; case OPT_MINBZA: if (val < 0 || val > 359) goto USAGE; min_branch_z_ang = (int)val; break; case OPT_SHRINK: if (val < 0 || val > 1) goto USAGE; len_shrink = val; break; case OPT_THICK: if (val < 2) goto USAGE; thickness = val; break; case OPT_SWAY: if (val < 0) goto USAGE; sway_ang = (int)val; break; case OPT_SWAYDIV: if (val < 0) goto USAGE; sway_div = (int)val; break; case OPT_PERS: do_pers = true; break; case OPT_CLIP: do_clip = true; break; case OPT_DBE: use_dbe = true; break; case OPT_INFO: show_info = true; break; case OPT_DELAY: if (val < 0) goto USAGE; delay = (int)val; break; case OPT_HELP: puts("\nKeys:\n" " R - Recreate new scene\n" " Q - Quit\n\n" " A - Decrease perspective half distance\n" " Z - Increase above\n\n" " S - Increase scaling\n" " X - Decrease scaling\n\n" " F - Move Z clip into screen\n" " V - Move Z clip out of screen\n\n" " U - Rotate anticlockwise\n" " I - Stop rotating\n" " O - Rotate clockwise\n\n" " L - Toggle clipping\n" " Y - Toggle perspective\n\n" " Arrow up - Move Z viewpoint into screen\n" " Arrow down - Move Z viewpoint out of screen\n" " Arrow left - Move left\n" " Arrow right - Move right\n\n" " Page up - Move up\n" " Page down - Move down\n\n" "Mouse:\n" " Button 1 - Rotate scene\n"); break; default: goto USAGE; } } if (max_levels < min_levels) { puts("ERROR: Max levels must be >= min levels."); exit(1); } if (max_len < min_len) { puts("ERROR: Max length must be >= min length."); exit(1); } if (max_branches < min_branches) { puts("ERROR: Max branches must be >= min branches."); exit(1); } if (max_branch_z_ang < min_branch_z_ang) { puts("ERROR: Max branch Z angle must be >= min branch Z angle."); exit(1); } return; USAGE: printf("Usage: %s\n" " -d \n" " -w : Default = %d\n" " -h : Default = %d\n" " -depth : Default = %d\n" " -trees : Default = %d\n" " -tprob 100) Default = %d\n" " -maxlev : Default = %d\n" " -minlev : Default = %d\n" " -maxlen : Default = %d\n" " -minlen : Default = %d\n" " -maxbr : Default = %d\n" " -minbr : Default = %d\n" " -maxbza : (0 -> 359) Default = %d\n" " -minbza : (0 -> 359) Default = %d\n" " -shrink : Default = %f\n" " -thick : (Min = 2) Default = %d\n" " -sway : Default = 0\n" " -swaydiv : Default = %d\n" " -pers : Add perspective\n" " -clip : Do clipping in Z plane\n" " -dbe : Use double buffer extension\n" " -info : Show runtime info in stdout\n" " -delay : Default = %d usecs\n" "All arguments are optional.\n", argv[0], WIDTH, HEIGHT, DEPTH, NUM_TREES, CONT_TRUNK_PROB, MAX_LEVELS, MIN_LEVELS, MAX_LEN, MIN_LEN, MAX_BRANCHES, MIN_BRANCHES, MAX_BRANCH_Z_ANG, MIN_BRANCH_Z_ANG, LEN_SHRINK, THICKNESS, SWAY_DIV, DELAY); exit(1); } /*** Initialise X stuff ***/ void X_init() { Window rootwin; XGCValues gcvals; XEvent event; Colormap cmap; XColor col; XColor unused; XTextProperty title_prop; int screen,white,black; int cnt; unsigned char r,g,b; char ra,ga,ba; char colstr[5]; char *title = (char *)"Forest"; if (!(display=XOpenDisplay(disp))) { printf("ERROR: Can't connect to display %s\n",XDisplayName(disp)); exit(1); } screen = DefaultScreen(display); rootwin = RootWindow(display,screen); cmap = DefaultColormap(display,screen); white = WhitePixel(display,screen); black = BlackPixel(display,screen); win = XCreateSimpleWindow(display,rootwin,0,0,width,height,0,white,black); if (use_dbe) { drw = (Drawable)XdbeAllocateBackBufferName(display,win,XdbeBackground); swapinfo.swap_window = win; swapinfo.swap_action = XdbeBackground; } else drw = win; // Create GCs r = 12; g = 0; b = 4; ga = 0; ra = -2; ba = 2; gcvals.background = black; for(cnt=0;cnt < NUM_COLOURS;++cnt) { sprintf(colstr,"#%01X%01X%01X",r,g,b); if (!XAllocNamedColor(display,cmap,colstr,&col,&unused)) { printf("WARNING: Can't allocate colour %s\n",colstr); col.pixel = white; } gcvals.foreground = col.pixel; gcol[cnt] = XCreateGC(display,win,GCForeground | GCBackground,&gcvals); b += ba; g += ga; r += ra; if (r == 0) ra = 0; if (b == 14) { ba = -2; ga = 2; } else if (b == 0) ba = 0; if (g == 14) ga = -2; } // Create black GC if (!use_dbe) { gcvals.foreground = black; gcb = XCreateGC(display,win,GCForeground | GCBackground,&gcvals); } XSelectInput( display,win, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); /* Set window title */ XStringListToTextProperty(&title,1,&title_prop); XSetWMProperties(display,win,&title_prop,NULL,NULL,0,NULL,NULL,NULL); /* Map window & wait for first expose */ XMapWindow(display,win); XNextEvent(display,&event); } /*** Set program specific stuff up ***/ void init() { struct timeval tv; double ang; int i; gettimeofday(&tv,NULL); srandom(tv.tv_usec); set_win_vars(); // Set up sine & cosine tables for(ang=0;ang < 360;++ang) { sinang[(int)ang] = sin(ang / DEGS_PER_RADIAN); cosang[(int)ang] = cos(ang / DEGS_PER_RADIAN); } num_branches = 0; draw_cnt = 0; x_viewpoint = 0; y_viewpoint = 0; z_viewpoint = 0; z_clippoint = 0; x_rot_ang = 0; y_rot_ang = 0; y_ang_add = 0; scale = 1; half_dist = depth * 2; // Create the trees tree = new cl_branch*[num_trees]; for(i=0;i < num_trees;++i) tree[i] = new cl_branch(NULL,0,0); // Set up branches list for doing z sorts branch = new cl_branch*[num_branches]; } /*** Start here ***/ void mainloop() { Window rw,cw; XEvent event; KeySym ksym; int rx,ry; u_int mask; char key; int i; bool recalc; bool rotate; for(recalc=true,rotate = false;;) { // Get rid of any expose events XCheckTypedEvent(display,Expose,&event); // Check for button pressed or released if (XCheckTypedEvent(display,ButtonPress,&event)) { switch(event.xbutton.button) { case 1: last_ptr_x = -1; last_ptr_y = -1; rotate = true; break; } } if (XCheckTypedEvent(display,ButtonRelease,&event) && event.xbutton.button == 1) rotate = false; if (XCheckTypedEvent(display,ConfigureNotify,&event)) { width = event.xconfigure.width; height = event.xconfigure.height; set_win_vars(); if (!use_dbe) XClearWindow(display,win); recalc = true; } if (XCheckTypedEvent(display,KeyPress,&event)) { XLookupString(&event.xkey,&key,1,&ksym,NULL); switch(ksym) { case XK_q: case XK_Q: exit(0); case XK_r: case XK_R: reset(); recalc = true; break; case XK_a: case XK_A: half_dist -= HD_INC; break; case XK_z: case XK_Z: half_dist += HD_INC; break; case XK_s: case XK_S: scale += SCALE_INC; break; case XK_x: case XK_X: scale -= SCALE_INC; break; case XK_f: case XK_F: z_clippoint += CLIP_INC; break; case XK_v: case XK_V: z_clippoint -= CLIP_INC; break; case XK_u: case XK_U: y_ang_add = 1; break; case XK_i: case XK_I: y_ang_add = 0; break; case XK_o: case XK_O: y_ang_add = -1; break; case XK_l: case XK_L: do_clip = !do_clip; break; case XK_y: case XK_Y: do_pers = !do_pers; break; case XK_Left: x_viewpoint += X_VP_INC; break; case XK_Right: x_viewpoint -= X_VP_INC; break; case XK_Up: z_viewpoint -= Z_VP_INC; break; case XK_Down: z_viewpoint += Z_VP_INC; break; case XK_Page_Up: y_viewpoint -= X_VP_INC; break; case XK_Page_Down: y_viewpoint += X_VP_INC; break; } if (!use_dbe) XClearWindow(display,win); recalc = true; } if (rotate) XQueryPointer(display,win,&rw,&cw,&rx,&ry,&ptr_x,&ptr_y,&mask); if (rotate || y_ang_add) set_rotation_angles(); if (rotate || y_ang_add || recalc || sway_ang) { for(i=0,draw_cnt=0;i < num_trees;++i) tree[i]->calculate(); z_sort(); recalc = false; } for(i=0;i < draw_cnt;++i) branch[i]->draw(true); if (use_dbe) XdbeSwapBuffers(display,&swapinfo,1); else XFlush(display); if (show_info) { printf("\rXrot: %03d Yrot: %03d Zview: %04d Scale: %02.02f Clip: %s,%04d Pers: %s,%04d ", x_rot_ang, y_rot_ang, z_viewpoint, scale, do_clip ? " ON" : "OFF", z_clippoint, do_pers ? " ON" : "OFF", half_dist); fflush(stdout); } usleep(delay); if (!use_dbe) { for(i=0;i < draw_cnt;++i) branch[i]->draw(false); } } } /******************************* Run functions ******************************/ /*** Resets everything and creates a new forest ***/ void reset() { int i; XClearWindow(display,win); for(i=0;i < num_trees;++i) delete tree[i]; delete[] tree; delete[] branch; init(); } /*** Sort the branches into Z order. Furthest away must get drawn first ***/ void z_sort() { cl_branch *tmp; int i,j,maxpos; for(i=0;i < draw_cnt-1;++i) { maxpos = i; for(j=i+1;j < draw_cnt;++j) if (branch[j]->zavg > branch[maxpos]->zavg) maxpos = j; if (maxpos != i) { tmp = branch[i]; branch[i] = branch[maxpos]; branch[maxpos] = tmp; } } } /*** Set some window vars ***/ void set_win_vars() { width2 = width / 2; height2 = height / 2; } /*** Set up the view rotation angles ***/ void set_rotation_angles() { if (y_ang_add) { y_rot_ang = (y_rot_ang + y_ang_add) % 360; if (y_rot_ang < 0) y_rot_ang += 360; } if (last_ptr_x == -1 && last_ptr_y == -1) { last_ptr_x = ptr_x; last_ptr_y = ptr_y; } else { y_rot_ang = (y_rot_ang + (ptr_x < last_ptr_x) - (ptr_x > last_ptr_x)); last_ptr_x = ptr_x; x_rot_ang = (x_rot_ang + (ptr_y < last_ptr_y) - (ptr_y > last_ptr_y)); last_ptr_y = ptr_y; } if (x_rot_ang >= 360) while(x_rot_ang >= 360) x_rot_ang -= 360; else while(x_rot_ang < 0) x_rot_ang += 360; if (y_rot_ang >= 360) while(y_rot_ang >= 360) y_rot_ang -= 360; else while(y_rot_ang < 0) y_rot_ang += 360; } /*** Rotate the given point by the given angles ***/ void rotate_point( double &x, double &y, double &z, int x_rot, int y_rot, int z_rot) { double xt,yt; // Rotate about Z axis xt = x; x = x * cosang[z_rot] + y * sinang[z_rot]; y = y * cosang[z_rot] - xt * sinang[z_rot]; // Rotate about Y axis xt = x; x = x * cosang[y_rot] + z * sinang[y_rot]; z = z * cosang[y_rot] - xt * sinang[y_rot]; // Rotate about X axis yt = y; y = y * cosang[x_rot] - z * sinang[x_rot]; z = yt * sinang[x_rot] + z * cosang[x_rot]; } /*** Get perspective multiplier ***/ double get_pmult(double z) { return do_pers ? 1 / (pow(2,z / half_dist)) : 1; } /*** Get a random value between (and including) a min and max value ***/ int get_random(int min, int max) { return min + (random() % (max - min + 1)); }