/***************************************************************************** SHOAL: Demonstrates simple shoaling & flocking in virtual fish. If they're shoaling they mill around the average centre of mass of the shoal or of a local group (depending on the parameters set) unless a predator (the mouse) appears or they flock. If action is "both" then they switch between these modes at random. Original version: November 2004 Last update : March 2005 *****************************************************************************/ #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 500 #define HEIGHT 500 #define MAX_SPEED 10 #define LINE_LEN 10 #define MAX_TURN_ANG 15 #define ANG_TOLERANCE 40 #define PRED_ANG_TOLERANCE 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 DELAY 10000 #define SPD_RST_TO_AVG 5 #define MAX_SWITCH_AFTER 100 enum { DO_SHOAL, DO_FLOCK, DO_BOTH } action; Display *display; char *disp; Window win; GC gcb,gcw; Cursor pirate; int width; int height; double xcent; double ycent; 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 pred_x,pred_y; int pred_tol_ang; int scared_dist; int delay; int spd_rst_to_avg; int max_switch_after; double line_len; double local_max_dist; double shoal_speed; double shoal_angle; double accel_mult; double decel_mult; bool inroot; bool winwrap; bool predator; double sinang[360]; double cosang[360]; void parse_cmd_line(int argc, char **argv); void X_init(); void init(); void mainloop(); void get_shoal_data(); double get_angle(double x1, double y1, double x2, double y2); double angle_diff(double ang1, double ang2); int angle_dir(double ang1, double ang2); extern "C" { void do_exit(int sig); } /***************************** Fish class *******************************/ class cl_fish { public: double x,y; double 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 white); void inc_angle(double amt); void set_goal_speed(double amt); } **fish; /*** Constructor ***/ cl_fish::cl_fish() { x = random() % width; y = random() % height; 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; draw(false); // If outside window turn back in if (x < 0 || y < 0 || x > width || y > height) wrap_or_forceback(); else // If predator near then run away if (predator && (dist = hypot(x - pred_x, y - pred_y)) < 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(random() % random_angle - (random_angle / 2)); set_goal_speed(speed + (random() % random_speed) - 1); } } set_speed_and_move(); draw(true); // 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_angle(x,y,pred_x,pred_y); if (angle_diff(ang,pang) < pred_tol_ang) inc_angle(max_turn_ang * -angle_dir(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) while(y < height) y += height; else if (y > height) while(y > height) y -= height; return; } if (x < 0 && y < 0) ang2 = get_angle(x,y,0,0); else if (x < 0 && y > height) ang2 = get_angle(x,y,0,height); else if (x < 0) ang2 = 90; else if (x > width && y < 0) ang2 = get_angle(x,y,width,0); else if (x > width && y > height) ang2 = get_angle(x,y,width,height); else if (x > width) ang2 = 270; else if (y < 0) ang2 = 0; else if (y > height) ang2 = 180; else { puts("ERROR in cl_fish::wrap_or_forceback()"); exit(1); } inc_angle(max_turn_ang * angle_dir(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; double atc,angd,spd,avang; int i,first,cnt; // Get distance to local centre spd = 0; avang = 0; cnt = 0; xf = 0; yf = 0; first = -1; for(i=0;i < num_fish;++i) { if (fish[i] != this && hypot(x - fish[i]->x,y - fish[i]->y) <= local_search_dist) { ++cnt; xf += fish[i]->x; yf += fish[i]->y; 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 avang += angle_diff(fish[i]->ang,fish[first]->ang) * angle_dir(fish[i]->ang,fish[first]->ang); } } } if (cnt < local_density) return 0; xf /= cnt; yf /= cnt; spd /= cnt; if ((dist = hypot(x - xf, y - yf)) > local_max_dist) { atc = get_angle(x,y,xf,yf); if ((angd = angle_diff(ang,atc)) > tol_ang) { inc_angle((angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(ang,atc)); } if (!(random() % spd_rst_to_avg)) set_goal_speed(spd); } else if (do_flock) { avang = fish[first]->ang + avang / cnt; if ((angd = angle_diff(ang,avang)) > tol_ang) { inc_angle((angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(ang,avang)); } 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 = hypot(x - xcent, y - ycent)) > cent_dist) { atc = get_angle(x,y,xcent,ycent); if ((angd = angle_diff(ang,atc)) > tol_ang) { inc_angle((angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(ang,atc)); } if (!(random() % spd_rst_to_avg)) set_goal_speed(shoal_speed); } else if (do_flock) { if ((angd = angle_diff(ang,shoal_angle)) > tol_ang) { inc_angle((angd < max_turn_ang ? angd : max_turn_ang) * angle_dir(ang,shoal_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() { 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; } } x += speed * sinang[(int)ang]; y += speed * cosang[(int)ang]; } /*** Draw the fish ***/ void cl_fish::draw(bool white) { double x2,y2; x2 = x - line_len * sinang[(int)ang]; y2 = y - line_len * cosang[(int)ang]; XDrawLine( display, win,white ? gcw : gcb,(int)x,(int)y,(int)x2,(int)y2); } /*** Increment the angle and allow for looping around ***/ void cl_fish::inc_angle(double amt) { ang += amt; if (ang >= 360) while(ang >= 360) ang -= 360; else while(ang < 0) ang += 360; } /*** 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", "num", "len", "maxsp", "maxta", "accel", "decel", "tolang", "tolpang", "random", "ranang", "ransp", "cdist", "sdist", "lsdist", "lmdist", "density", "srta", "delay", "action", "msa", "wrap" }; enum { OPT_D, OPT_INROOT, OPT_W, OPT_H, OPT_NUM, OPT_LEN, 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_MSA, OPT_WRAP, OPT_END }; int i,o; double val; disp = NULL; inroot = false; width = WIDTH; height = HEIGHT; num_fish = NUM_FISH; line_len = LINE_LEN, 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_ANG_TOLERANCE; 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; 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: 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; --i; 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_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_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_MSA: if (val < 1) goto USAGE; max_switch_after = (int)val; break; case OPT_WRAP: winwrap = true; --i; break; default: goto USAGE; } } return; USAGE: printf("Usage: %s\n" " -d \n" " -inroot (Run in root window)\n" " -w (Overidden by -inroot)\n" " -h (Overidden by -inroot)\n" " -num \n" " -len \n" " -maxsp \n" " -maxta \n" " -accel \n" " -tolpang \n" " -random (100%% = totally random)\n" " -ranang \n" " -ransp \n" " -cdist \n" " -sdist \n" " -lsdist \n" " -lmdist \n" " -density \n" " -srta \n" " -delay \n" " -action shoal/flock/both (Type of action for fish to carry out)\n" " -msa (1-N)\n" " -wrap (Make window wrap)\n",argv[0]); exit(1); } /*** Initialise X stuff ***/ void X_init() { Window rootwin; XGCValues gcvals; XColor blue,unused; XEvent event; Colormap cmap; XTextProperty title_prop; int screen,white; char *title = (char *)"SHOAL"; if (!(display=XOpenDisplay(disp))) { printf("XOpenDisplay(): Can't connect to: %s\n",XDisplayName(disp)); exit(1); } screen = DefaultScreen(display); rootwin = RootWindow(display,screen); cmap = DefaultColormap(display,screen); white = WhitePixel(display,screen); XAllocNamedColor(display,cmap,"#000066",&blue,&unused); if (inroot) { width = DisplayWidth(display,screen); height = DisplayHeight(display,screen); win = rootwin; XSetWindowBackground(display,win,blue.pixel); } else win = XCreateSimpleWindow(display,rootwin,0,0,width,height,0,white,blue.pixel); gcvals.foreground = blue.pixel; gcvals.background = white; gcb = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); gcvals.foreground = white; gcvals.background = blue.pixel; gcw = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); // If we're in the root window just clear it and naff off if (inroot) { XClearWindow(display,win); return; } XSelectInput( display,win, ExposureMask | 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; 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); } } /*** Run the creatures and check for mouse movements ***/ void mainloop() { Window rw,cw; XEvent event; int i,rx,ry; u_int mask; while(1) { // 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) { predator = true; XDefineCursor(display,win,pirate); } } else if (XCheckTypedEvent(display,ButtonRelease,&event)) { if (predator && event.xbutton.button == 1) { predator = false; XUndefineCursor(display,win); } } else if (XCheckTypedEvent(display,ConfigureNotify,&event)) { width = event.xconfigure.width; height = event.xconfigure.height; } if (predator) XQueryPointer( display, win,&rw,&cw,&rx,&ry,&pred_x,&pred_y,&mask); } // Run fish get_shoal_data(); for(i=0;i < num_fish;++i) fish[i]->run(); XFlush(display); usleep(delay); } } /**************************** Support functions ***************************/ /*** 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; shoal_speed = 0; shoal_angle = 0; for(i=0;i < num_fish;++i) { xcent += fish[i]->x; ycent += fish[i]->y; shoal_speed += fish[i]->speed; if (action != DO_SHOAL && i) shoal_angle += angle_diff(fish[i]->ang,fish[0]->ang) * angle_dir(fish[i]->ang,fish[0]->ang); } xcent /= num_fish; ycent /= num_fish; shoal_speed /= num_fish; if (action != DO_SHOAL) shoal_angle = fish[0]->ang + shoal_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_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 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; } /*** Reset the cursor ***/ void do_exit(int sig) { if (predator) XUndefineCursor(display,win); exit(0); }