/***************************************************************************** SHOAL3D: A 3D version of the shoal program where the fish can swim in all dimensions. An optional box can be drawn around them to give a better sense of perspective and show the their boundaries. Written by Neil Robertson Version: 20050429 Compiling: CC/c++ -L/usr/X11R6/lib -lX11 -lXext -lm [-D SOLARIS_BUG] Keys: P - Pause Q - Quit A - Increase perspective half distance Z - Decrease above S - Increase scaling factor X - Decrease above D - Move clipping point away from viewer C - Move towards viewer F - Move predator blob away from viewer V - Move towards viewer Arrow up - Move viewpoint up Arrow down - Move viewpoint down Arrow left - Move viewpoint left Arrow right - Move viewpoint right Mouse: Button 1 - Rotate scene Button 2/3 - Place predator *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEGS_PER_RADIAN 57.29578 // Set up some defaults that make an interesting effect #define NUM_FISH 100 #define WIDTH 300 #define HEIGHT 300 #define TAILCOL "green" #define HEADCOL "yellow" #define BOXCOL "white" #define BACKCOL "#000066" #define MAX_SPEED 10 #define HEAD_DIAM 4 #define LINE_LEN 10 #define LINE_THICK 1 #define BOX_LINE_THICK 1 #define MAX_TURN_ANG 15 #define ANG_TOLERANCE 40 #define PRED_TOL_ANG 160 #define CENT_DIST 50 #define SCARED_DIST 100 #define LOCAL_SEARCH_DIST 100 #define LOCAL_MAX_DIST 35 #define RANDOM_PCT 10 #define RANDOM_ANGLE 30 #define RANDOM_SPEED 3 #define LOCAL_DENSITY 6 #define ACCEL_MULT 0.2 #define DECEL_MULT 0.3 #define SPD_RST_TO_AVG 5 #define MAX_SWITCH_AFTER 100 #define HD_DEPTH_MULT 3 #define DELAY 10000 #define HALF_DIST 1000 #define HD_INC 5 #define VP_INC 5 #define CLIP_INC 5 #define Z_PRED_INC 5 #define SCALE_INC 0.02 #define ROT_MULT 1 #define PRED_DIAM 10 #define CIRCLE 23040 /* 360 * 64 */ enum { DO_SHOAL, DO_FLOCK, DO_BOTH } action; Display *display; char *disp; Window win; XdbeSwapInfo swapinfo; Drawable drw; GC gch,gct,gcbx; GC gcfbg,gcbg,gcr; Cursor pirate; char *tailcol; char *headcol; char *boxcol; char *backcol; int width; int height; int depth; int width2; int height2; int depth2; int num_fish; int g_max_speed; int g_max_turn_ang; int tol_ang; int cent_dist; int local_search_dist; int local_density; int random_pct; int random_angle; int random_speed; int ptr_x,ptr_y; int pred_z; int pred_tol_ang; int scared_dist; int delay; int spd_rst_to_avg; int max_switch_after; int full_z_angle; int last_ptr_x; int last_ptr_y; int half_dist; int x_viewpoint; int y_viewpoint; int z_clippoint; int rot_mult; int line_thick; int box_line_thick; double xcent; double ycent; double zcent; double line_len; double head_diam; double local_max_dist; double shoal_speed; double shoal_xy_angle; double shoal_z_angle; double accel_mult; double decel_mult; double scale; double y_rot_ang; double x_rot_ang; double auto_x_rot_add; double auto_y_rot_add; bool inroot; bool winwrap; bool predator; bool manual_rotate; bool do_perspective; bool do_clip; bool do_box; bool use_dbe; bool paused; bool show_info; double sinang[360]; double cosang[360]; void parse_cmd_line(int argc, char **argv); void X_init(); void init(); void mainloop(); void set_win_vars(); void set_rotation_angles(); void get_shoal_data(); double get_xy_angle(double x1, double y1, double x2, double y2); double get_z_angle(double hyp,double z1,double z2,double x1,double x2); void inc_angle(double *iang, double amt); double angle_diff(double ang1, double ang2); int angle_dir(double ang1, double ang2); double hypot3(double x, double y, double z); double get_pmult(double z); void draw_box(GC gc); void draw_line(GC gc, double mx, double my, double mz, double mx2, double my2, double mz2); void draw_blob(GC gc, double diameter, double x, double y, double z); void project(double mx, double my, double mz, double *dx, double *dy, double *dz); extern "C" { void do_exit(int sig); } /***************************** Fish class *******************************/ class cl_fish { public: double x,y,z; double xy_ang; double z_ang; double speed; double max_speed; double goal_speed; double max_turn_ang; double accel; double decel; bool do_flock; int ticker; int switch_after; cl_fish(); void run(); void flee_from_predator(double dist); void wrap_or_forceback(); int move_to_local_cent(); void move_to_cent(); void set_speed_and_move(); void draw(bool erase); void set_goal_speed(double amt); } **fish; /*** Constructor ***/ cl_fish::cl_fish() { x = random() % width; y = random() % height; z = random() % depth; xy_ang = random() % 360; z_ang = random() % 360; max_speed = (random() % g_max_speed) + 1; speed = goal_speed = (random() % (int)max_speed) + 1; max_turn_ang = (random() % g_max_turn_ang) + 1; accel = max_speed * accel_mult; decel = max_speed * decel_mult; switch(action) { case DO_SHOAL: do_flock = false; break; case DO_FLOCK: do_flock = true; break; case DO_BOTH: do_flock = random() % 2 ? true : false; ticker = 0; switch_after = (random() % max_switch_after) + 1; } } /*** Run the individual fish ***/ void cl_fish::run() { double dist; if (!paused) { // If outside window turn back in if (x < 0 || y < 0 || z < 0 || x > width || y > height || z > depth) wrap_or_forceback(); else // If predator near then run away if (predator && (dist = hypot3(x - ptr_x, y - ptr_y, z - pred_z)) < scared_dist) flee_from_predator(dist); else { if (!move_to_local_cent()) move_to_cent(); // Add random element if (random() % 101 <= random_pct) { inc_angle(&xy_ang,random() % random_angle - (random_angle / 2)); set_goal_speed(speed + (random() % random_speed) - 1); } } set_speed_and_move(); } draw(false); // Switch over to other action if its time if (action == DO_BOTH && ++ticker == switch_after) { ticker = 0; do_flock = !do_flock; } } /*** Run from a predator (the mouse pointer) ***/ void cl_fish::flee_from_predator(double dist) { double pang,spd,mult; mult = (1 - dist / scared_dist); pang = get_xy_angle(x,y,ptr_x,ptr_y); if (angle_diff(xy_ang,pang) < pred_tol_ang) inc_angle(&xy_ang,max_turn_ang * -angle_dir(xy_ang,pang) * mult); pang = get_z_angle(dist,z,pred_z,x,ptr_x); if (angle_diff(z_ang,pang) < pred_tol_ang) inc_angle(&z_ang,max_turn_ang * -angle_dir(z_ang,pang) * mult); if ((spd = max_speed * mult) > speed) set_goal_speed(spd); } /*** Wrap window for fish or turn them back towards visible window area ***/ void cl_fish::wrap_or_forceback() { double ang2; if (winwrap) { // Have loops since a major window resize could shove fish more // than a screens width/height outside window edge if (x < 0) x += width; else if (x > width) x -= width; if (y < 0) y += height; else if (y > height) y -= height; if (z < 0) z += depth; else if (z > depth) z -= depth; return; } if (x < 0 && y < 0) ang2 = get_xy_angle(x,y,0,0); else if (x < 0 && y > height) ang2 = get_xy_angle(x,y,0,height); else if (x < 0) ang2 = 90; else if (x > width && y < 0) ang2 = get_xy_angle(x,y,width,0); else if (x > width && y > height) ang2 = get_xy_angle(x,y,width,height); else if (x > width) ang2 = 270; else if (y < 0) ang2 = 0; else if (y > height) ang2 = 180; inc_angle(&xy_ang,max_turn_ang * angle_dir(xy_ang,ang2)); if (z < 0 || z > depth) { ang2 = (z < 0 ? 90 : 270); inc_angle(&z_ang,max_turn_ang * angle_dir(z_ang,ang2)); } } /*** Move to a local centre of fish if we're at required density ***/ int cl_fish::move_to_local_cent() { double dist,xf,yf,zf; double atc,angd,spd; double av_xy_ang,av_z_ang; int i,first,cnt; // Get distance to local centre spd = 0; av_xy_ang = 0; av_z_ang = 0; cnt = 0; xf = 0; yf = 0; first = -1; for(i=0;i < num_fish;++i) { if (fish[i] != this && hypot3(x - fish[i]->x,y - fish[i]->y,z - fish[i]->z) <= local_search_dist) { ++cnt; xf += fish[i]->x; yf += fish[i]->y; zf += fish[i]->z; spd += fish[i]->speed; /* Numeric avaerage of angles doesn't give correct answer for the average angle. eg (350 + 10) / 2 = 180 , not 0, so we sum differences from the initial angle and divide by cnt */ if (do_flock) { if (first == -1) first = i; else { av_xy_ang += angle_diff(fish[i]->xy_ang,fish[first]->xy_ang) * angle_dir(fish[i]->xy_ang,fish[first]->xy_ang); av_z_ang += angle_diff(fish[i]->z_ang,fish[first]->z_ang) * angle_dir(fish[i]->z_ang,fish[first]->z_ang); } } } } if (cnt < local_density) return 0; xf /= cnt; yf /= cnt; zf /= cnt; spd /= cnt; if ((dist = hypot3(x - xf, y - yf, z - zf)) > local_max_dist) { atc = get_xy_angle(x,y,xf,yf); if ((angd = angle_diff(xy_ang,atc)) > tol_ang) inc_angle(&xy_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(xy_ang,atc)); atc = get_z_angle(dist,z,zf,x,xf); if ((angd = angle_diff(z_ang,atc)) > tol_ang) inc_angle(&z_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(z_ang,atc)); if (!(random() % spd_rst_to_avg)) set_goal_speed(spd); } else if (do_flock) { av_xy_ang = fish[first]->xy_ang + av_xy_ang / cnt; if ((angd = angle_diff(xy_ang,av_xy_ang)) > tol_ang) inc_angle(&xy_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(xy_ang,av_xy_ang)); av_z_ang = fish[first]->z_ang + av_z_ang / cnt; if ((angd = angle_diff(z_ang,av_z_ang)) > tol_ang) inc_angle(&z_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(z_ang,av_z_ang)); if (!(random() % spd_rst_to_avg)) set_goal_speed(spd); } return 1; } /*** Turn and move towards shoal centre ***/ void cl_fish::move_to_cent() { double dist,atc,angd; // If fish decides its too far away from shoal centre turn back towards it if ((dist = hypot3(x - xcent, y - ycent, z - zcent)) > cent_dist) { atc = get_xy_angle(x,y,xcent,ycent); if ((angd = angle_diff(xy_ang,atc)) > tol_ang) inc_angle(&xy_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(xy_ang,atc)); atc = get_z_angle(dist,z,zcent,x,xcent); if ((angd = angle_diff(z_ang,atc)) > tol_ang) inc_angle(&z_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(z_ang,atc)); if (!(random() % spd_rst_to_avg)) set_goal_speed(shoal_speed); } else if (do_flock) { if ((angd = angle_diff(xy_ang,shoal_xy_angle)) > tol_ang) inc_angle(&xy_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(xy_ang,shoal_xy_angle)); if ((angd = angle_diff(z_ang,shoal_z_angle)) > tol_ang) inc_angle(&z_ang,(angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(z_ang,shoal_xy_angle)); if (!(random() % spd_rst_to_avg)) set_goal_speed(shoal_speed); } } /*** Set actual speed and move fishy ***/ void cl_fish::set_speed_and_move() { double xy_speed; if (speed != goal_speed) { if (speed < goal_speed) { if (goal_speed - speed < accel) speed = goal_speed; else speed += accel; } else { if (speed - goal_speed < decel) speed = goal_speed; else speed -= decel; } } xy_speed = fabs(speed * cosang[(int)z_ang]); x += xy_speed * sinang[(int)xy_ang]; y += xy_speed * cosang[(int)xy_ang]; z += speed * sinang[(int)z_ang]; } /*** Draw the fish ***/ void cl_fish::draw(bool erase) { double mx,my,mz; double mx2,my2,mz2; double len; // Draw the head if (head_diam) draw_blob(erase ? gcbg : gch,head_diam,x,y,z); // Normalise so that 0,0 is centre of window so we can rotate around // the X & Y axis properly mx = x - width2; my = y - height2; mz = z - depth2; // Get end of line point len = fabs(line_len * cosang[(int)z_ang]); mx2 = mx - len * sinang[(int)xy_ang]; my2 = my - len * cosang[(int)xy_ang]; mz2 = mz - line_len * sinang[(int)z_ang]; draw_line(erase ? gcbg : gct,mx,my,mz,mx2,my2,mz2); } /*** Set the speed we wish to reach ***/ void cl_fish::set_goal_speed(double spd) { goal_speed = (spd > max_speed ? max_speed : spd); if (goal_speed < 1) goal_speed = 1; } /******************************* 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", "inroot", "w", "h", "tc", "hc", "bxc", "bgc", "num", "len", "thk", "head", "maxsp", "maxta", "accel", "decel", "tolang", "tolpang", "random", "ranang", "ransp", "cdist", "sdist", "lsdist", "lmdist", "density", "srta", "delay", "action", "rotmult", "axrot", "ayrot", "msa", "wrap", "info", "lza", "box", "bthk", "pers", "clip", "dbe" }; enum { OPT_D, OPT_INROOT, OPT_W, OPT_H, OPT_TC, OPT_HC, OPT_BXC, OPT_BGC, OPT_NUM, OPT_LEN, OPT_THK, OPT_HEAD, OPT_MAXSP, OPT_MAXTA, OPT_ACCEL, OPT_DECEL, OPT_TOLANG, OPT_TOLPANG, OPT_RANDOM, OPT_RANANG, OPT_RANSP, OPT_CDIST, OPT_SDIST, OPT_LSDIST, OPT_LMDIST, OPT_DENSITY, OPT_SRTA, OPT_DELAY, OPT_ACTION, OPT_ROTMULT, OPT_AXROT, OPT_AYROT, OPT_MSA, OPT_WRAP, OPT_INFO, OPT_LZA, OPT_BOX, OPT_BTHK, OPT_PERS, OPT_CLIP, OPT_DBE, OPT_END }; int i,o; double val; disp = NULL; inroot = false; width = WIDTH; height = HEIGHT; tailcol = (char *)TAILCOL; headcol = (char *)HEADCOL; boxcol = (char *)BOXCOL; backcol = (char *)BACKCOL; num_fish = NUM_FISH; line_len = LINE_LEN, line_thick = LINE_THICK; box_line_thick = BOX_LINE_THICK; head_diam = HEAD_DIAM; cent_dist = CENT_DIST; g_max_speed = MAX_SPEED; g_max_turn_ang = MAX_TURN_ANG; tol_ang = ANG_TOLERANCE; random_pct = RANDOM_PCT; random_angle = RANDOM_ANGLE; random_speed = RANDOM_SPEED; accel_mult = ACCEL_MULT; decel_mult = DECEL_MULT; pred_tol_ang = PRED_TOL_ANG; local_search_dist = LOCAL_SEARCH_DIST; local_max_dist = LOCAL_MAX_DIST; local_density = LOCAL_DENSITY; scared_dist = SCARED_DIST; spd_rst_to_avg = SPD_RST_TO_AVG; delay = DELAY; winwrap = false; action = DO_SHOAL; max_switch_after = MAX_SWITCH_AFTER; show_info = false; full_z_angle = 1; do_box = false; do_clip = false; do_perspective = false; rot_mult = ROT_MULT; auto_x_rot_add = 0; auto_y_rot_add = 0; use_dbe = 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_INROOT: case OPT_WRAP: case OPT_INFO: case OPT_LZA: case OPT_BOX: case OPT_PERS: case OPT_CLIP: case OPT_DBE: --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_INROOT: inroot = true; 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_TC: tailcol = argv[i+1]; break; case OPT_HC: headcol = argv[i+1]; break; case OPT_BXC: boxcol = argv[i+1]; break; case OPT_BGC: backcol = argv[i+1]; break; case OPT_NUM: if (val < 1) goto USAGE; num_fish = (int)val; break; case OPT_LEN: if (val < 1) goto USAGE; line_len = val; break; case OPT_THK: if (val < 1) goto USAGE; line_thick = (int)val; break; case OPT_HEAD: if (val < 0) goto USAGE; head_diam = val; break; case OPT_MAXSP: if (val < 1) goto USAGE; g_max_speed = (int)val; break; case OPT_MAXTA: if (val < 0 || val > 360) goto USAGE; g_max_turn_ang = (int)val; break; case OPT_ACCEL: if (val < 0 || val > 1) goto USAGE; accel_mult = val; break; case OPT_DECEL: if (val < 0 || val > 1) goto USAGE; decel_mult = val; break; case OPT_TOLANG: if (val < 0 || val > 180) goto USAGE; tol_ang = (int)val; break; case OPT_TOLPANG: if (val < 0 || val > 180) goto USAGE; pred_tol_ang = (int)val; break; case OPT_RANDOM: if (val < 0 || val > 100) goto USAGE; random_pct = (int)val; break; case OPT_RANANG: if (val < 0 || val > 360) goto USAGE; random_angle = (int)val; break; case OPT_RANSP: if (val < 0) goto USAGE; random_speed = (int)val; break; case OPT_CDIST: if (val < 1) goto USAGE; cent_dist = (int)val; break; case OPT_SDIST: if (val < 1) goto USAGE; scared_dist = (int)val; break; case OPT_LSDIST: if (val < 0) goto USAGE; local_search_dist = (int)val; break; case OPT_LMDIST: if (val < 0) goto USAGE; local_max_dist = val; break; case OPT_DENSITY: if (val < 0) goto USAGE; local_density = (int)val; break; case OPT_SRTA: if (val < 0) goto USAGE; spd_rst_to_avg = (int)val; break; case OPT_DELAY: if (val < 0) goto USAGE; delay = (int)val; break; case OPT_ACTION: if (!strcasecmp(argv[i+1],"shoal")) action = DO_SHOAL; else if (!strcasecmp(argv[i+1],"flock")) action = DO_FLOCK; else if (!strcasecmp(argv[i+1],"both")) action = DO_BOTH; else goto USAGE; break; case OPT_ROTMULT: if (val < 1) goto USAGE; rot_mult = (int)val; break; case OPT_AXROT: auto_x_rot_add = val; break; case OPT_AYROT: auto_y_rot_add = val; break; case OPT_MSA: if (val < 1) goto USAGE; max_switch_after = (int)val; break; case OPT_WRAP: winwrap = true; break; case OPT_INFO: show_info = true; break; case OPT_LZA: full_z_angle = 0; break; case OPT_BOX: do_box = true; break; case OPT_BTHK: if (val < 1) goto USAGE; box_line_thick = (int)val; break; case OPT_PERS: do_perspective = true; break; case OPT_CLIP: do_clip = true; break; case OPT_DBE: use_dbe = true; break; default: goto USAGE; } } set_win_vars(); return; USAGE: printf("Usage: %s\n" " -d \n" " -inroot : Run in root window\n" " -w : Overidden by -inroot\n" " -h : Overidden by -inroot\n" " -tc \n" " -hc \n" " -bxc \n" " -bgc \n" " -num \n" " -len \n" " -thk \n" " -head \n" " -maxsp \n" " -maxta \n" " -accel \n" " -decel \n" " -tolang \n" " -tolpang \n" " -random : 100%% = totally random\n" " -ranang \n" " -ransp \n" " -cdist \n" " -sdist \n" " -lsdist \n" " -lmdist \n" " -density \n" " -srta \n" " -action shoal/flock/both : Type of action for fish to carry out\n" " -rotmult \n" " -axrot : Auto x axis rotation per iteration\n" " -ayrot : Auto y axis rotation per iteration\n" " -msa \n" " -wrap : Make window wrap\n" " -info : Show info\n" " -lza : Limit z angle range (-90 -> 0 -> 90)\n" " -box : Draw bounding box\n" " -bthk \n" " -pers : Add perspective\n" " -clip : Clip lines for z < 0\n" " -dbe : Use double buffer extension\n" " -delay \n" ,argv[0]); exit(1); } /*** Initialise X stuff ***/ void X_init() { Window rootwin; XGCValues gcvals; XColor tcol,hcol,bxcol,bgcol; XColor red,unused; XEvent event; Colormap cmap; XTextProperty title_prop; int screen; char *title = (char *)"3D SHOAL"; 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); if (!XAllocNamedColor(display,cmap,headcol,&hcol,&unused)) { printf("ERROR: Can't allocate colour '%s'\n",headcol); exit(1); } if (!XAllocNamedColor(display,cmap,tailcol,&tcol,&unused)) { printf("ERROR: Can't allocate colour '%s'\n",tailcol); exit(1); } if (!XAllocNamedColor(display,cmap,boxcol,&bxcol,&unused)) { printf("ERROR: Can't allocate colour '%s'\n",boxcol); exit(1); } if (!XAllocNamedColor(display,cmap,backcol,&bgcol,&unused)) { printf("ERROR: Can't allocate colour '%s'\n",backcol); exit(1); } if (!XAllocNamedColor(display,cmap,"red",&red,&unused)) { puts("ERROR: Can't allocate colour 'red'\n"); exit(1); } if (inroot) { width = DisplayWidth(display,screen); height = DisplayHeight(display,screen); win = rootwin; XSetWindowBackground(display,win,bgcol.pixel); } else win = XCreateSimpleWindow( display,rootwin,0,0,width,height,0,tcol.pixel,bgcol.pixel); if (use_dbe) { drw = (Drawable)XdbeAllocateBackBufferName(display,win,XdbeBackground); swapinfo.swap_window = win; swapinfo.swap_action = XdbeBackground; } else drw = win; gcvals.foreground = red.pixel; gcvals.background = bgcol.pixel; gcr = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); gcvals.foreground = hcol.pixel; gch = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); gcvals.foreground = tcol.pixel; gcvals.line_width = line_thick; gct = XCreateGC(display, win, GCForeground | GCBackground | GCLineWidth, &gcvals); gcvals.foreground = bgcol.pixel; gcfbg = XCreateGC(display, win, GCForeground | GCLineWidth, &gcvals); gcvals.foreground = bxcol.pixel; gcvals.line_width = box_line_thick; gcbx = XCreateGC(display, win, GCForeground | GCBackground | GCLineWidth, &gcvals); gcvals.foreground = bgcol.pixel; gcbg = XCreateGC(display, win, GCForeground | GCLineWidth, &gcvals); // If we're in the root window just clear it and naff off if (inroot) { XClearWindow(display,win); return; } 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); /* Create pirate cursor */ pirate = XCreateFontCursor(display,XC_pirate); /* Map window & wait for first expose */ XMapWindow(display,win); XNextEvent(display,&event); } /*** Set program specific stuff up ***/ void init() { int i; double ang; signal(SIGQUIT,do_exit); signal(SIGINT,do_exit); signal(SIGTERM,do_exit); predator = false; manual_rotate = false; paused = false; x_rot_ang = 0; y_rot_ang = 0; last_ptr_x = -1; last_ptr_y = -1; x_viewpoint = 0; y_viewpoint = 0; z_clippoint = -depth2; scale = 1; srandom(time(0)); fish = new cl_fish*[num_fish]; for(i=0;i < num_fish;++i) fish[i] = new cl_fish(); xcent = 0; ycent = 0; shoal_speed = 0; // 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); } } /*** Reset the cursor ***/ void do_exit(int sig) { if (predator) XUndefineCursor(display,win); exit(0); } /*** Run the creatures and check for mouse movements ***/ void mainloop() { Window rw,cw; XEvent event; KeySym ksym; int i,rx,ry; u_int mask; char key; bool wincleared; while(1) { wincleared = false; // Only window manager can get events from root window if (!inroot) { // Get rid of any expose events XCheckTypedEvent(display,Expose,&event); // Check for button pressed or released if (XCheckTypedEvent(display,ButtonPress,&event)) { if (event.xbutton.button == 1) { last_ptr_x = -1; last_ptr_y = -1; manual_rotate = true; } else { predator = true; XDefineCursor(display,win,pirate); } } if (XCheckTypedEvent(display,ButtonRelease,&event)) { if (event.xbutton.button == 1) manual_rotate = false; else if (predator) { predator = false; if (!use_dbe) draw_blob(gcbg,PRED_DIAM,ptr_x,ptr_y,pred_z); XUndefineCursor(display,win); } } if (XCheckTypedEvent(display,ConfigureNotify,&event)) { width = event.xconfigure.width; height = event.xconfigure.height; set_win_vars(); if (!use_dbe) { XClearWindow(display,win); wincleared = true; } } if (XCheckTypedEvent(display,KeyPress,&event)) { XLookupString(&event.xkey,&key,1,&ksym,NULL); switch(ksym) { case XK_p: case XK_P: paused = !paused; break; case XK_q: case XK_Q: exit(0); 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_d: case XK_D: z_clippoint += CLIP_INC; break; case XK_c: case XK_C: z_clippoint -= CLIP_INC; break; case XK_f: case XK_F: pred_z = (pred_z + Z_PRED_INC) % depth; break; case XK_v: case XK_V: pred_z -= Z_PRED_INC; if (pred_z < 0) pred_z += depth; break; case XK_Left: x_viewpoint -= VP_INC; break; case XK_Right: x_viewpoint += VP_INC; break; case XK_Up: y_viewpoint -= VP_INC; break; case XK_Down: y_viewpoint += VP_INC; break; } if (!use_dbe) { XClearWindow(display,win); wincleared = true; } } } // Undraw if (!use_dbe && !wincleared) { if (predator) draw_blob(gcbg,PRED_DIAM,ptr_x,ptr_y,pred_z); if (do_box) draw_box(gcbg); for(i=0;i < num_fish;++i) fish[i]->draw(true); } if (predator || manual_rotate) { XQueryPointer( display, win,&rw,&cw,&rx,&ry,&ptr_x,&ptr_y,&mask); } // Calculate and draw new positions set_rotation_angles(); get_shoal_data(); for(i=0;i < num_fish;++i) fish[i]->run(); if (do_box) draw_box(gcbx); if (predator) draw_blob(gcr,PRED_DIAM,ptr_x,ptr_y,pred_z); if (use_dbe) XdbeSwapBuffers(display,&swapinfo,1); else XFlush(display); // Show some info if (show_info) { printf("\rScale: %03.02f X,Y ang: %06.02f,%06.02f Half dist: %04d Clip: %04d ", scale,x_rot_ang,y_rot_ang, do_perspective ? half_dist : 0,do_clip ? z_clippoint : 0); fflush(stdout); } usleep(delay); } } /**************************** Support functions ***************************/ /*** Set some window vars ***/ void set_win_vars() { depth = (width + height) / 2; width2 = width / 2; height2 = height / 2; depth2 = pred_z = depth / 2; half_dist = depth * HD_DEPTH_MULT; } /*** Set up the view rotation angles ***/ void set_rotation_angles() { x_rot_ang += auto_x_rot_add; y_rot_ang += auto_y_rot_add; if (manual_rotate) { 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) * rot_mult - (ptr_x > last_ptr_x) * rot_mult); last_ptr_x = ptr_x; x_rot_ang = (x_rot_ang + (ptr_y > last_ptr_y) * rot_mult - (ptr_y < last_ptr_y) * rot_mult); 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; } /*** Get the centre, average speed and average angle of the shoal ***/ void get_shoal_data() { int i; // Get distance to centre of shoal xcent = 0; ycent = 0; zcent = 0; shoal_speed = 0; shoal_xy_angle = 0; shoal_z_angle = 0; for(i=0;i < num_fish;++i) { xcent += fish[i]->x; ycent += fish[i]->y; zcent += fish[i]->z; shoal_speed += fish[i]->speed; if (action != DO_SHOAL) { if (i) { shoal_xy_angle += angle_diff(fish[i]->xy_ang,fish[0]->xy_ang) * angle_dir(fish[i]->xy_ang,fish[0]->xy_ang); shoal_z_angle += angle_diff(fish[i]->z_ang,fish[0]->z_ang) * angle_dir(fish[i]->z_ang,fish[0]->z_ang); } } } xcent /= num_fish; ycent /= num_fish; zcent /= num_fish; shoal_speed /= num_fish; if (action != DO_SHOAL) { shoal_xy_angle = fish[0]->xy_ang + shoal_xy_angle / num_fish; shoal_z_angle = fish[0]->z_ang + shoal_z_angle / num_fish; } } /*** Get the angle of x2,y2 in relation to x1,y1 (y goes from bottom to top as far as this is concerned, not the X windows top to bottom). Eg if x1,y1 = 10,10 and x2,y2 = 20,20 it returns 45, not 135. ***/ double get_xy_angle(double x1, double y1, double x2, double y2) { double xd = x2 - x1; double yd = y2 - y1; double ang; if (!yd) return (xd > 0 ? 90 : 270); // Horizontal line if (!xd) return (yd > 0 ? 0 : 180); // Vertical line ang = atan(xd/yd) * DEGS_PER_RADIAN; return (yd < 0 ? 180 + ang : (xd > 0 ? ang : 360 + ang)); } /*** Get z angle. Use x positions to decide if angle will be between 270 -> 0 -> 90 and 90 -> 180 -> 270 ***/ double get_z_angle(double hyp,double z1,double z2,double x1,double x2) { // Get -90 -> 0 -> 90 double ang = asin((z2 - z1) / hyp) * DEGS_PER_RADIAN; if (full_z_angle && x1 < x2) // Reflect angle on 90 -> 270 line. Eg 80 becomes 110, 0 becomes 180 ang = 90 + (90 - ang); else { // Convert -90 -> 0 -> 90 to 270 -> 0 -> 90 ang += 360; if (ang >= 360) ang -= 360; } return ang; } /*** Increment the angle and allow for looping around ***/ void inc_angle(double *iang, double amt) { *iang += amt; if (*iang >= 360) while(*iang >= 360) *iang -= 360; else while(*iang < 0) *iang += 360; } /*** Get the difference of angle 1 from angle 2. Eg if ang2 = 10 & ang1 = 80 this returns 70 .This assumes ang1 and ang2 are in the range 0 - 360 or it won't work. ***/ double angle_diff(double ang2, double ang1) { // Place ang2 at zero degrees and work out ang1 from there ang1 -= ang2; if (ang1 < 0) ang1 += 360; return ang1 < 180 ? ang1 : 360 - ang1; } /*** Return the direction to increment angle 1 in to get angle 2. Makes 0 - 360 assumption as above. ***/ int angle_dir(double ang1, double ang2) { ang1 -= ang2; if (ang1 < 0) ang1 += 360; return ang1 < 180 ? -1 : 1; } /*** Get 3D hypotenuse. With SOLARIS_BUG I'm assigning the result of sqrt() to a variable first as there seems to be an obscure Solaris/compiler combination bug that occurs when the result from sqrt() is put straight on the stack which causes further calls to sqrt() cause invalid values. ***/ double hypot3(double x, double y, double z) { #ifdef SOLARIS_BUG double dist = sqrt(x*x + y*y + z*z); return dist; #endif return sqrt(x*x + y*y + z*z); } /*** Get perspective multiplier ***/ double get_pmult(double z) { return do_perspective ? 1 / (pow(2,z / half_dist)) : 1; } /**************************** Drawing functions *******************************/ /*** Draw bounding box ***/ void draw_box(GC gc) { // Horizontals // Left bottom line (y is 0 at top of window) draw_line(gc,-width2,height2,-depth2,-width2,height2,depth2); // Far bottom draw_line(gc,-width2,height2,depth2,width2,height2,depth2); // Right bottom draw_line(gc,width2,height2,-depth2,width2,height2,depth2); // Near bottom draw_line(gc,-width2,height2,-depth2,width2,height2,-depth2); // Left top draw_line(gc,-width2,-height2,-depth2,-width2,-height2,depth2); // Far top draw_line(gc,-width2,-height2,depth2,width2,-height2,depth2); // Right top draw_line(gc,width2,-height2,-depth2,width2,-height2,depth2); // Near top draw_line(gc,-width2,-height2,-depth2,width2,-height2,-depth2); // Verticals // Left far draw_line(gc,-width2,height2,depth2,-width2,-height2,depth2); // Right far draw_line(gc,width2,height2,depth2,width2,-height2,depth2); // Left near draw_line(gc,-width2,height2,-depth2,-width2,-height2,-depth2); // Right near draw_line(gc,width2,height2,-depth2,width2,-height2,-depth2); } /*** Draw a line in the window after doing transformations ***/ void draw_line(GC gc, double mx, double my, double mz, double mx2, double my2, double mz2) { double dx,dy,dz,dx2,dy2,dz2; double ratio,thick; project(mx,my,mz,&dx,&dy,&dz); project(mx2,my2,mz2,&dx2,&dy2,&dz2); if (do_clip) { if (dz < z_clippoint) { if (dz2 < z_clippoint) return; ratio = (dz2 - z_clippoint) / (dz2 - dz); dx = (dx2 + (dx - dx2) * ratio); dy = (dy2 + (dy - dy2) * ratio); } else if (dz2 < z_clippoint) { if (dz < z_clippoint) return; ratio = (dz - z_clippoint) / (dz - dz2); dx2 = (dx + (dx2 - dx) * ratio); dy2 = (dy + (dy2 - dy) * ratio); } } // Change line thickness depending on distance away thick = (double)line_thick * scale * get_pmult(dz); XSetLineAttributes( display,gc,thick < 1 ? 1 : (int)thick,LineSolid,CapRound,JoinRound); XDrawLine(display,drw,gc,(int)dx,(int)dy,(int)dx2,(int)dy2); } /*** Draw a blob ***/ void draw_blob(GC gc, double diameter, double x, double y, double z) { double dx,dy,dz; double diam,radius; x -= width2; y -= height2; z -= depth2; project(x,y,z,&dx,&dy,&dz); // Change head diameter depending on distance diam = diameter * scale * get_pmult(dz); if (diam < 1) diam = 1; radius = diam / 2; if (!do_clip || dz >= z_clippoint) XFillArc( display,drw,gc, (int)(dx - radius),(int)(dy - radius),(int)diam,(int)diam, 0,CIRCLE); } /*** Projects the normalised x,y,z point into 3D window space by rotating and adding perspective then returns the draw point dx,dy ***/ void project( double mx, double my, double mz, double *dx, double *dy, double *dz) { double xt,yt; double pmult; // Rotate about Y axis xt = mx; mx = mx * cosang[(int)y_rot_ang] + mz * sinang[(int)y_rot_ang]; mz = mz * cosang[(int)y_rot_ang] - xt * sinang[(int)y_rot_ang]; // Rotate about X axis yt = my; my = my * cosang[(int)x_rot_ang] - mz * sinang[(int)x_rot_ang]; mz = yt * sinang[(int)x_rot_ang] + mz * cosang[(int)x_rot_ang]; /* Add perspective. This works on the halfway pricipal. At HALF_DIST objects will be half size , at HALF_DIST * 2 they will be 1/4 , at HALF_DIST * 3 they will be 1/8 etc. At -HALF_DIST they will be 2 , 4, 8 and so forth. */ pmult = get_pmult(mz); *dx = mx * pmult * scale + width2 - x_viewpoint; *dy = my * pmult * scale + height2 - y_viewpoint; *dz = mz; }