/***************************************************************************** RIPPLES: 3D simulation of the surface of a membrane or fluid. May 2005: Original. Aug 2006: Added new colours and colour cycling and renamed functions. Oct 2006: Added continuous kick while moving mouse pointer. Added z_scale & reformatted to Allman style. Written by Neil Robertson Version: 20061020 Compiling: CC/c++ -L/usr/X11R6/lib -lX11 -lXext -lm Keys: P - Pause R - Reset Q - Quit A - Decrease perspective half distance Z - Increase above S - Move towards surface X - Move away from surface F - Move Z clip into screen V - Move Z clip out of screen G - Increase colour adjust B - Decrease colour adjust > - Increase kick amount < - Decrease kick amount 1 - Decrease colour cycling amount 2 - Increase colour cycling amount U - Rotate anticlockwise I - Stop rotating O - Rotate clockwise C - Toggle wave colour D - Switch between dots, lines and polygons E - Toggle free of edges H - Toggle hide static edges J - Flip direction of kick K - Iterate through kick types L - Toggle clipping T - Toggle colour cycling Y - Toggle perspective Arrow up - Move viewpoint up Arrow down - Move down Arrow left - Move left Arrow right - Move right Mouse: Button 1 - Rotate grid Button 2 - Add island at grid location Button 3 - Kick grid location *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #define DEGS_PER_RADIAN 57.29578 #define CIRCLE 23040 #define WIDTH 300 #define HEIGHT 300 #define GRID_X_SIZE 50 #define GRID_Y_SIZE 50 #define THICKNESS 1 #define HD_INC 10 #define VP_INC 5 #define KICK_INC 2 #define CLIP_INC 2 #define SCALE_INC 0.01 #define HD_DEPTH_MULT 3 #define ROT_MULT 2 #define X_START_ANG 315 #define KICK_FORCE 5 #define KICK_WIDTH 10 #define MASS 2 #define INV_FRICTION 1 #define CONSTANT_FORCE 5 #define NUM_COLOURS 90 #define NUM_STRESS_COLOURS 76 // Mid stress not exactly halfway which means a slight imbalance in colour // variation above and below sheet but this means the mid colour is blue // instead of a sickly mauve #define MID_STRESS_COLOUR 30 #define COLOUR_ADJUST 8 #define ISLAND_WIDTH 5 #define ISLAND_HEIGHT 5 #define ISLAND_COLOUR "brown" #define DELAY 10000 Display *display; char *disp; Window win; XdbeSwapInfo swapinfo; Drawable drw; GC gcw,gcb,gci,gcc[NUM_COLOURS]; int width; int height; int orig_width; int orig_height; int depth; int width2; int height2; int grid_x_size; int grid_y_size; int max_grid_x; int max_grid_y; int delay; int x_viewpoint; int y_viewpoint; int ptr_x; int ptr_y; int last_ptr_x; int last_ptr_y; int half_dist; int rot_mult; int thickness; int kick_type; int force_type; int hide_edges; int kick_flip; int colour_adjust; int col_cyc_add; int draw_type; int z_rot_ang; int z_ang_add; int z_add; int clipline; int island_width; int island_height; double kick_force; double kick_width; double x_point_seperation; double y_point_seperation; double y_rot_ang; double x_rot_ang; double x_start_ang; double mass; double inv_friction; double constant_force; double scale; double z_scale; bool do_col; bool do_clip; bool do_perspective; bool do_col_cyc; bool rotate; bool use_dbe; bool show_info; bool paused; bool free_from_edges; double sinang[360]; double cosang[360]; enum { UP, DOWN, LEFT, RIGHT } ; enum { KICK_DEPRESSION, KICK_CRATER, KICK_LEDGE, KICK_CROSS, KICK_END }; enum { FORCE_LINEAR, FORCE_LOG, FORCE_LOG10, FORCE_CONSTANT }; enum { DRAW_DOTS, DRAW_LINES, DRAW_POLYGONS, DRAW_END }; void parseCmdLine(int argc, char **argv); void Xinit(); void init(); void mainloop(); void setWinVars(); void setRotationAngles(bool rotate); void addIsland(); void kickGrid(); void calculate(); void project(); void drawGrid(bool erase); void reset(); double getPmult(double z); double sgn(double val); bool getMouseGridPos(int &gx, int &gy); bool clipLine(double &x, double &y, double &z, double &x2, double &y2, double &z2); bool isVisible(int x, int y); GC getGC(double z); void drawPolygon(GC gc,int gx, int gy); void drawLine(GC gc, double dx, double dy, double dz, double dx2, double dy2, double dz2); void drawDot(GC gc, double dx, double dy, double dz); /******************************* Point class *******************************/ class cl_point { public: double z_vel; double x,y,z; double dx,dy,dz; double force[4]; int gx; int gy; bool fixed; cl_point(int gx, int gy); void reset(); void setXY(); bool inIsland(); void calcForces(); void addVelocity(); void project(); void draw(bool erase); } ***point; /*** Constructor ***/ cl_point::cl_point(int grid_x, int grid_y) { gx = grid_x; gy = grid_y; reset(); } /*** Reset everything ***/ void cl_point::reset() { dx = dy = dz = z = 0; z_vel = 0; bzero(force,sizeof(force)); setXY(); fixed = (!gx || !gy || gx == max_grid_x || gy == max_grid_y); } /*** Set the true x & y positions ***/ void cl_point::setXY() { x = (double)gx * x_point_seperation; y = (double)gy * y_point_seperation; } /*** Returns true if we're part of an island ***/ bool cl_point::inIsland() { return (fixed && gx && gy && gx < max_grid_x && gy < max_grid_y); } /*** Calculate the right and down force of this point. This is also the left and up force of adjacent points. Don't alter actual z position here as it'll mess up calculates of other grid locations ***/ void cl_point::calcForces() { double frc; // Alter our right and right neighbours left if (gx < max_grid_x) { if (free_from_edges && (fixed || point[gx+1][gy]->fixed)) frc = 0; else frc = point[gx+1][gy]->z - z; switch(force_type) { case FORCE_LINEAR: force[RIGHT] = frc; break; case FORCE_LOG: if (frc) force[RIGHT] = log(fabs(frc)) * sgn(frc); else force[RIGHT] = 0; break; case FORCE_LOG10: if (frc) force[RIGHT] = log10(fabs(frc)) * sgn(frc); else force[RIGHT] = 0; break; case FORCE_CONSTANT: force[RIGHT] = constant_force * sgn(frc); } point[gx+1][gy]->force[LEFT] = -force[RIGHT]; } // Alter our down and below neighbours up if (gy < max_grid_y) { if (free_from_edges && (fixed || point[gx][gy+1]->fixed)) frc = 0; else frc = point[gx][gy+1]->z - z; switch(force_type) { case FORCE_LINEAR: force[DOWN] = frc; break; case FORCE_LOG: if (frc) force[DOWN] = log(fabs(frc)) * sgn(frc); else force[DOWN] = 0; break; case FORCE_LOG10: if (frc) force[DOWN] = log10(fabs(frc)) * sgn(frc); else force[DOWN] = 0; break; case FORCE_CONSTANT: force[DOWN] = constant_force * sgn(frc); } point[gx][gy+1]->force[UP] = -force[DOWN]; } // Don't alter velocity of edge points if (!fixed) z_vel += (force[UP] + force[DOWN] + force[LEFT] + force[RIGHT]) / 4 / mass; } /*** Add the velocity to z ***/ void cl_point::addVelocity() { if (!fixed) z = (z + z_vel) * inv_friction; } /*** Project the normalised x,y,z point into 3D window space by rotating, scaling and adding perspective ***/ void cl_point::project() { double xt,yt; double pmult; dx = x - width2; dy = y - height2; dz = z * z_scale; // z_scale takes account of window resizing // Rotate about Z axis xt = dx; dx = dx * cosang[z_rot_ang] + dy * sinang[z_rot_ang]; dy = dy * cosang[z_rot_ang] - xt * sinang[z_rot_ang]; // Rotate about Y axis xt = dx; dx = dx * cosang[(int)y_rot_ang] + dz * sinang[(int)y_rot_ang]; dz = dz * cosang[(int)y_rot_ang] - xt * sinang[(int)y_rot_ang]; // Rotate about X axis yt = dy; dy = dy * cosang[(int)x_rot_ang] - dz * sinang[(int)x_rot_ang]; dz = yt * sinang[(int)x_rot_ang] + dz * 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. */ dz += z_add; pmult = getPmult(dz); dx = dx * scale * pmult + width2 - x_viewpoint; dy = dy * scale * pmult + height2 - y_viewpoint; } /*** Draw lines to the right and below ***/ void cl_point::draw(bool erase) { cl_point *pnt; GC gc; if (hide_edges && fixed) return; switch(draw_type) { case DRAW_DOTS: gc = erase ? gcb : (inIsland() ? gci : getGC(z)); drawDot(gc,dx,dy,dz); return; case DRAW_LINES: // Draw to point on the right if (gx < max_grid_x - hide_edges) { pnt = point[gx+1][gy]; if (!hide_edges || !pnt->fixed) { if (inIsland() && pnt->inIsland()) gc = gci; else gc = erase ? gcb : getGC((z + pnt->z)/2); drawLine(gc,dx,dy,dz,pnt->dx,pnt->dy,pnt->dz); } } // Draw to point below if (gy < max_grid_y - hide_edges) { pnt = point[gx][gy+1]; if (!hide_edges || !pnt->fixed) { gc = erase ? gcb : (inIsland() && pnt->inIsland() ? gci : getGC((z + pnt->z)/2)); drawLine(gc,dx,dy,dz,pnt->dx,pnt->dy,pnt->dz); } } return; case DRAW_POLYGONS: if (gx < max_grid_x - hide_edges && gy < max_grid_y - hide_edges) { gc = erase ? gcb : (inIsland() && point[gx+1][gy]->inIsland() && point[gx][gy+1]->inIsland() && point[gx+1][gy+1]->inIsland() ? gci : getGC((z + point[gx+1][gy]->z + point[gx][gy+1]->z + point[gx+1][gy+1]->z) / 4)); drawPolygon(gc,gx,gy); } } } /******************************* Start here ********************************/ int main(int argc, char **argv) { parseCmdLine(argc,argv); Xinit(); init(); mainloop(); return 0; } /*** Parse the command line ***/ void parseCmdLine(int argc, char **argv) { const char *opt[] = { "d", "w", "h", "gx", "gy", "kick", "kwidth", "ktype", "iw", "ih", "force", "fcon", "mass", "fric", "xsang", "thk", "draw", "col", "colcyc", "coladj", "clip", "pers", "free", "hide", "dbe", "info", "delay", "help" }; enum { OPT_D, OPT_W, OPT_H, OPT_GX, OPT_GY, OPT_KICK, OPT_KWIDTH, OPT_KTYPE, OPT_IW, OPT_IH, OPT_FORCE, OPT_FCON, OPT_MASS, OPT_FRIC, OPT_XSANG, OPT_THK, OPT_DRAW, OPT_COL, OPT_COLCYC, OPT_COLADJ, OPT_CLIP, OPT_PERS, OPT_FREE, OPT_HIDE, OPT_DBE, OPT_INFO, OPT_DELAY, OPT_HELP, OPT_END }; int i,o; double val; disp = NULL; orig_width = width = WIDTH; orig_height = height = HEIGHT; thickness = THICKNESS; kick_force = KICK_FORCE; kick_width = KICK_WIDTH; kick_type = KICK_DEPRESSION; force_type = FORCE_LINEAR; constant_force = CONSTANT_FORCE; mass = MASS; inv_friction = INV_FRICTION; grid_x_size = GRID_X_SIZE; grid_y_size = GRID_Y_SIZE; rot_mult = ROT_MULT; do_col = false; do_clip = false; do_perspective = false; do_col_cyc = false; colour_adjust = COLOUR_ADJUST; free_from_edges = false; hide_edges = false; draw_type = DRAW_LINES; island_width = ISLAND_WIDTH; island_height = ISLAND_HEIGHT; show_info = false; delay = DELAY; use_dbe = false; clipline = 0; x_start_ang = X_START_ANG; 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_COL: case OPT_COLCYC: case OPT_FREE: case OPT_HIDE: case OPT_DBE: case OPT_INFO: case OPT_PERS: 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_GX: if (val < 2) goto USAGE; grid_x_size = (int)val; break; case OPT_GY: if (val < 2) goto USAGE; grid_y_size = (int)val; break; case OPT_KICK: if (val < 0) goto USAGE; kick_force = val; break; case OPT_KWIDTH: if (val < 0) goto USAGE; kick_width = val; break; case OPT_KTYPE: if (!strcasecmp(argv[i+1],"depr")) kick_type = KICK_DEPRESSION; else if (!strcasecmp(argv[i+1],"crater")) kick_type = KICK_CRATER; else if (!strcasecmp(argv[i+1],"ledge")) kick_type = KICK_LEDGE; else if (!strcasecmp(argv[i+1],"cross")) kick_type = KICK_CROSS; else goto USAGE; break; case OPT_IW: if (val < 0) goto USAGE; island_width = (int)val; break; case OPT_IH: if (val < 0) goto USAGE; island_height = (int)val; break; case OPT_FORCE: if (!strcasecmp(argv[i+1],"linear")) force_type = FORCE_LINEAR; else if (!strcasecmp(argv[i+1],"log")) force_type = FORCE_LOG; else if (!strcasecmp(argv[i+1],"log10")) force_type = FORCE_LOG10; else if (!strcasecmp(argv[i+1],"const")) force_type = FORCE_CONSTANT; else goto USAGE; break; case OPT_FCON: if (val < 0) goto USAGE; constant_force = val; break; case OPT_MASS: if (val < 1) goto USAGE; mass = val; break; case OPT_FRIC: if (val < 0 || val > 1) goto USAGE; inv_friction = 1 - val; break; case OPT_XSANG: if (val < 0 || val > 359) goto USAGE; x_start_ang = val; break; case OPT_THK: if (val < 1) goto USAGE; thickness = (int)val; break; case OPT_DRAW: if (!strcasecmp(argv[i+1],"dots")) draw_type = DRAW_DOTS; else if (!strcasecmp(argv[i+1],"lines")) draw_type = DRAW_LINES; else if (!strcasecmp(argv[i+1],"polys")) draw_type = DRAW_POLYGONS; else goto USAGE; break; case OPT_COL: do_col = true; break; case OPT_COLCYC: do_col_cyc = true; break; case OPT_COLADJ: if (val < 0) goto USAGE; colour_adjust = (int)val; break; case OPT_CLIP: do_clip = true; clipline = (int)val; break; case OPT_PERS: do_perspective = true; break; case OPT_FREE: free_from_edges = true; break; case OPT_HIDE: hide_edges = 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" " P - Pause\n" " R - Reset\n" " Q - Quit\n\n" " A - Decrease perspective half distance\n" " Z - Increase above\n\n" " S - Move towards surface\n" " X - Move away from surface\n\n" " F - Move Z clip into screen\n" " V - Move Z clip out of screen\n\n" " G - Increase kick amount\n" " B - Decrease above\n\n" " U - Rotate anticlockwise\n" " I - Stop rotating\n" " O - Rotate clockwise\n\n" " C - Toggle wave colour\n" " D - Switch between dots, lines and polygons\n" " E - Toggle free of edges\n" " H - Toggle hide static edges\n" " J - Flip direction of kick\n" " K - Iterate through kick types\n" " L - Toggle clipping\n" " Y - Toggle perspective\n\n" " Arrow up - Move viewpoint up\n" " Arrow down - Move down\n" " Arrow left - Move left\n" " Arrow right - Move right\n\n" "Mouse:\n" " Button 1 - Rotate grid\n" " Button 2 - Add island at grid location\n" " Button 3 - Kick grid location\n"); break; default: goto USAGE; } } max_grid_x = grid_x_size; max_grid_y = grid_y_size; // Add one since this is actually the number of points , not squares grid_x_size++; grid_y_size++; return; USAGE: printf("Usage: %s\n" " -d \n" " -w : Default = %d\n" " -h : Default = %d\n" " -gx : Default = %d\n" " -gy : Default = %d\n" " -kick : Default = %d\n" " -kwidth : Default = %d\n" " -ktype depr | crater | ledge | cross : Kick type. Default = depr\n" " -iw : Default = %d\n" " -ih : Default = %d\n" " -force linear | log | log10 | const : Force calc method. Def. = linear\n" " -fcon : For 'const' option. Def. = %d\n" " -mass : Default = %d\n" " -fric 1) : Default = %d\n" " -xsang : Default = %d\n" " -thk : Default = %d\n" " -draw dots | lines | polys : Default = lines\n" " -col : Show amplitude colours\n" " -colcyc : Cycle amplitude colours\n" " -coladj : Default = %d\n" " -clip : Set Z clip. +ve = into screen.\n" " -pers : Add perspective\n" " -free : Allow to drift free from edges.\n" " -hide : Hide edge squares.\n" " -dbe : Use double buffer extension\n" " -info : Print runtime info to stdout\n" " -delay : Default = %d usecs\n" " -help : Shows help.\n" "All arguments are optional.\n", argv[0], WIDTH, HEIGHT, GRID_X_SIZE, GRID_Y_SIZE, KICK_FORCE, KICK_WIDTH, ISLAND_WIDTH, ISLAND_HEIGHT, CONSTANT_FORCE, MASS, 1 - INV_FRICTION, X_START_ANG, THICKNESS, COLOUR_ADJUST, DELAY); exit(1); } /*** Initialise X stuff ***/ void Xinit() { Window rootwin; XGCValues gcvals; XEvent event; Colormap cmap; XColor col; XColor unused; XTextProperty title_prop; int screen,white,black; int cnt,stage; unsigned char r,g,b; char colstr[5]; char *title = (char *)"Ripples"; 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 white GC gcvals.background = black; gcvals.line_width = thickness; gcvals.foreground = white; gcw = XCreateGC( display,win,GCForeground | GCBackground | GCLineWidth,&gcvals); // Create colour GCs r = 0; g = 0xF; b = 0; stage = 1; 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; gcc[cnt] = XCreateGC( display,win, GCForeground | GCBackground | GCLineWidth,&gcvals); switch(stage) { case 1: if (++b == 0xF) stage = 2; break; case 2: if (!--g) stage = 3; break; case 3: if (++r == 0xF) stage = 4; break; case 4: if (!--b) stage = 5; break; case 5: if (++g == 0xF) stage = 6; break; case 6: --r; } } // Create island gc if (!XAllocNamedColor(display,cmap,ISLAND_COLOUR,&col,&unused)) { printf("WARNING: Can't allocate colour %s\n",ISLAND_COLOUR); col.pixel = white; } gcvals.foreground = col.pixel; gci = XCreateGC( display,win,GCForeground | GCBackground | GCLineWidth,&gcvals); // Create black GC if (!use_dbe) { gcvals.foreground = black; gcb = XCreateGC( display,win, GCForeground | GCBackground | GCLineWidth,&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() { double ang; int gx,gy; // Create the grid point = new cl_point**[grid_x_size]; for(gx = 0;gx < grid_x_size;++gx) { point[gx] = new cl_point*[grid_y_size]; for(gy = 0;gy < grid_y_size;++gy) point[gx][gy] = new cl_point(gx,gy); } // 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(); } /*** Run the creatures and check for mouse movements ***/ void mainloop() { Window rw,cw; XEvent event; KeySym ksym; int rx,ry; u_int mask; char key; bool wincleared; bool kicked; bool island; for(kicked = false;;) { wincleared = false; island = 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; case 2: island = true; break; case 3: kicked = true; } } if (XCheckTypedEvent(display,ButtonRelease,&event)) { switch(event.xbutton.button) { case 1: rotate = false; break; case 3: kicked = false; } } if (XCheckTypedEvent(display,ConfigureNotify,&event)) { width = event.xconfigure.width; height = event.xconfigure.height; setWinVars(); 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_r: case XK_R: reset(); 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: clipline += CLIP_INC; break; case XK_v: case XK_V: clipline -= CLIP_INC; break; case XK_g: case XK_G: colour_adjust++; break; case XK_b: case XK_B: if (colour_adjust > 0) colour_adjust--; break; case XK_u: case XK_U: z_ang_add = 1; break; case XK_i: case XK_I: z_ang_add = 0; break; case XK_o: case XK_O: z_ang_add = -1; break; case XK_c: case XK_C: do_col = !do_col; break; case XK_d: case XK_D: draw_type = (draw_type + 1) % DRAW_END; break; case XK_e: case XK_E: free_from_edges = !free_from_edges; break; case XK_h: case XK_H: hide_edges = !hide_edges; break; case XK_k: case XK_K: kick_type = (kick_type + 1) % KICK_END; break; case XK_j: case XK_J: kick_flip = -kick_flip; break; case XK_l: case XK_L: do_clip = !do_clip; break; case XK_t: case XK_T: do_col_cyc = !do_col_cyc; break; case XK_y: case XK_Y: do_perspective = !do_perspective; break; case XK_period: case XK_greater: kick_force += KICK_INC; break; case XK_comma: case XK_less: kick_force -= KICK_INC; if (kick_force < 0) kick_force = 0; break; case XK_1: col_cyc_add--; if (col_cyc_add < 0) col_cyc_add = NUM_COLOURS - 1; break; case XK_2: col_cyc_add = (col_cyc_add + 1) % NUM_COLOURS; 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; } } if (rotate || kicked || island) XQueryPointer( display, win,&rw,&cw,&rx,&ry,&ptr_x,&ptr_y,&mask); // Undraw if (!use_dbe && !wincleared) drawGrid(true); // Calculate and draw new positions setRotationAngles(rotate); if (!paused) { if (island) addIsland(); if (kicked) kickGrid(); calculate(); } project(); drawGrid(false); if (use_dbe) XdbeSwapBuffers(display,&swapinfo,1); else XFlush(display); if (show_info) { printf("\rScale: %02.02f Clip: %03d HD: %04d Xang: %03.0f Yang: %03.0f Zang: %03d Kick: %03.0f ", scale, clipline, half_dist,x_rot_ang,y_rot_ang,z_rot_ang,kick_force); fflush(stdout); } if (do_col_cyc) col_cyc_add = (col_cyc_add + 1) % NUM_COLOURS; usleep(delay); } } /******************************* Run functions ******************************/ /*** Set some window vars ***/ void setWinVars() { int gx,gy; depth = (width + height) / 2; width2 = width / 2; height2 = height / 2; half_dist = depth * HD_DEPTH_MULT; x_point_seperation = (double)width / max_grid_x; y_point_seperation = (double)height / max_grid_y; // Take average difference of current width & height compared with // original z_scale = ((double)width / orig_width + (double)height / orig_height) / 2; for(gx = 0;gx < grid_x_size;++gx) for(gy = 0;gy < grid_y_size;++gy) point[gx][gy]->setXY(); } /*** Set up the view rotation angles ***/ void setRotationAngles(bool rotate) { if (z_ang_add) { z_rot_ang = (z_rot_ang + z_ang_add) % 360; if (z_rot_ang < 0) z_rot_ang += 360; } if (!rotate) return; 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; } /*** Add an island ***/ void addIsland() { int cx,cy; int gx,gy; int iw2,ih2; int wadd,hadd; if (!getMouseGridPos(cx,cy)) return; iw2 = island_width / 2; ih2 = island_height / 2; wadd = ((island_width % 2) != 0); hadd = ((island_height % 2) != 0); for(gx = cx - iw2;gx <= cx + iw2 + wadd;++gx) { if (gx < 1 || gx >= max_grid_x) continue; for(gy = cy - ih2;gy <= cy + ih2 + hadd;++gy) { if (gy > 0 && gy < max_grid_y) { point[gx][gy]->reset(); point[gx][gy]->fixed = true; } } } } /*** Calculate the position of the kick & kick it ***/ void kickGrid() { int kick_x,kick_y; int xf,yf; int xt,yt; int xp,yp; int k2; double sep,grid_dist,k; if (!getMouseGridPos(kick_x,kick_y)) return; // If its a crater type we want a rim so get algorithm to go beyond kick // width and create opposite kick forming a rim. if (kick_type == KICK_CRATER) { k2 = (int)kick_width; sep = kick_force / kick_width * 4; } else { k2 = (int)(kick_width / 2); sep = kick_force / kick_width * 2; } xf = kick_x - k2 + 1; yf = kick_y - k2 + 1; xt = kick_x + k2 - 1; yt = kick_y + k2 - 1; switch(kick_type) { case KICK_CROSS: for(xp = xf,yp = kick_y;xp <= xt;++xp) if (xp > 0 && xp < max_grid_x) point[xp][yp]->z_vel += ((kick_force - (sep * abs(kick_x - xp))) * kick_flip); // Fall through case KICK_LEDGE: for(yp = yf,xp = kick_x;yp <= yt;++yp) if (yp > 0 && yp < max_grid_y) point[xp][yp]->z_vel += ((kick_force - (sep * abs(kick_y - yp))) * kick_flip); break; case KICK_CRATER: case KICK_DEPRESSION: for(xp = xf;xp <= xt;++xp) { if (xp < 1 || xp >= max_grid_x) continue; for(yp = yf;yp <= yt;++yp) { if (yp < 1 || yp >= max_grid_y) continue; grid_dist = hypot(kick_x - xp, kick_y - yp); if (grid_dist <= kick_width) { k = kick_force - (sep * grid_dist); if (k > 0 || (kick_type == KICK_CRATER && k > -kick_force)) point[xp][yp]->z_vel += (k * kick_flip); } } } } } /*** Calculate all the new positions. Have to have all the forces updated before we set the new velocities. ***/ void calculate() { int gx,gy; for(gx = 0;gx < grid_x_size;++gx) for(gy = 0;gy < grid_y_size;++gy) point[gx][gy]->calcForces(); for(gx = 0;gx < grid_x_size;++gx) for(gy = 0;gy < grid_y_size;++gy) point[gx][gy]->addVelocity(); } /*** Call the projection method ***/ void project() { int gx,gy; for(gx = 0;gx < grid_x_size;++gx) for(gy = 0;gy < grid_y_size;++gy) point[gx][gy]->project(); } /*** Draw the actual grid ***/ void drawGrid(bool erase) { int xstart,ystart; int xend,yend; int xadd,yadd; int gx,gy; /* Find furthest corner and start drawing from there so we draw in the correct order and have stuff at the front obscuring stuff at the back. This is a dirty hack to save having to do a z sort and it doesn't always work */ if (point[0][0]->dz > point[max_grid_x][0]->dz) { xstart = 0; ystart = 0; xend = grid_x_size; yend = grid_y_size; xadd = 1; yadd = 1; } else { xstart = max_grid_x; ystart = 0; xend = -1; yend = grid_y_size; xadd = -1; yadd = 1; } if (point[0][max_grid_y]->dz > point[xstart][ystart]->dz) { xstart = 0; ystart = max_grid_y; xend = grid_y_size; yend = -1; xadd = 1; yadd = -1; } if (point[max_grid_x][max_grid_y]->dz > point[xstart][ystart]->dz) { xstart = max_grid_x; ystart = max_grid_y; xend = -1; yend = -1; xadd = -1; yadd = -1; } // We seem to get fewer immediate drawing artifacts if the X axis is // in the inside loop. Dunno why , shouldn't make any difference. for(gy = ystart;gy != yend;gy += yadd) { for(gx = xstart;gx != xend;gx += xadd) point[gx][gy]->draw(erase); } } /*** Reset everything ***/ void reset() { int gx,gy; paused = false; z_add = 0; x_rot_ang = x_start_ang; y_rot_ang = 0; z_rot_ang = 0; z_ang_add = 0; z_add = 0; x_viewpoint = 0; y_viewpoint = 0; kick_flip = 1; scale = 1; col_cyc_add = 0; setWinVars(); XClearWindow(display,win); for(gx = 0;gx < grid_x_size;++gx) for(gy = 0;gy < grid_y_size;++gy) point[gx][gy]->reset(); } /*** Get perspective multiplier ***/ double getPmult(double z) { return do_perspective ? 1 / (pow(2,z / half_dist)) : 1; } /** Return the sign of the number ***/ double sgn(double val) { return (val > 0 ? 1 : (val < 0 ? -1 : 0)); } /*** Get the grid position from where the mouse is and return true if its a valid position ***/ bool getMouseGridPos(int &gx, int &gy) { double x,y,xt; /* Offset x,y by half a grid width (so if a grid square is 10 pixels and we're at 4 we'll get 0 but if we're at 6 we'll get 1) and then normalise to 0,0 window centre for Z axis rotation */ x = (ptr_x + (x_point_seperation / 2)) - width2; y = (ptr_y + (y_point_seperation / 2)) - height2; xt = x; x = x * cosang[z_rot_ang] - y * sinang[z_rot_ang]; y = y * cosang[z_rot_ang] + xt * sinang[z_rot_ang]; // Denormalise and convert to grid x,y gx = (int)((x + width2) / x_point_seperation); gy = (int)((y + height2) / y_point_seperation); return (gx > 0 && gx < max_grid_x && gy > 0 && gy < max_grid_y); } /*** Clips a line. If line is completely invisible return false else true ***/ bool clipLine(double &x, double &y, double &z, double &x2, double &y2, double &z2) { double ratio; if (z < clipline) { if (z2 < clipline) return false; ratio = (z2 - clipline) / (z2 - z); x = x2 + (x - x2) * ratio; y = y2 + (y - y2) * ratio; } else if (z2 < clipline) { if (z < clipline) return false; ratio = (z - clipline) / (z - z2); x2 = x + (x2 - x) * ratio; y2 = y + (y2 - y) * ratio; } return true; } /*** Returns true if x,y co-ordinate is visible in the window ***/ bool isVisible(int x, int y) { return (x >=0 && x <= width && y >= 0 && y <= height); } /**************************** Drawing functions *******************************/ /*** Return the appropriate GC for this z position ***/ GC getGC(double z) { int col; if (do_col) { col = (int)((MID_STRESS_COLOUR) + (z * colour_adjust / MID_STRESS_COLOUR)); if (col >= NUM_STRESS_COLOURS) col = NUM_STRESS_COLOURS - 1; else if (col < 0) col = 0; col = (col + col_cyc_add) % NUM_COLOURS; return gcc[col]; } return gcw; } /*** Fill a polgon down and to the left of this point ***/ void drawPolygon(GC gc, int gx, int gy) { XPoint pnt[6]; double dx,dy,dz; double dx2,dy2,dz2; int xadd[] = { 0,1,1,0,0 }; int yadd[] = { 0,0,1,1,0 }; int xa,ya,i,cnt; if (do_clip) { /* A polygon is a point plus the 3 points to the east, southeast and south. Here we do the clipping which could mean we end up with more than 4 points as we could end up with 6 points if clipped on a corner (one join point becomes 2 unjoined) and if the last point is the same as the first */ for(i=0,cnt=0;i < 4;++i) { xa = xadd[i]; ya = yadd[i]; dx = point[gx+xa][gy+ya]->dx; dy = point[gx+xa][gy+ya]->dy; dz = point[gx+xa][gy+ya]->dz; xa = xadd[i+1]; ya = yadd[i+1]; dx2 = point[gx+xa][gy+ya]->dx; dy2 = point[gx+xa][gy+ya]->dy; dz2 = point[gx+xa][gy+ya]->dz; // If line is not beyond clipping line then set the // next 2 points to be its (clipped) beginning and end. if (clipLine(dx,dy,dz,dx2,dy2,dz2)) { // If the beginning of this line is the same as // the end of the last line just overwrite last // point. cnt -= (cnt && (short)dx == pnt[cnt-1].x && (short)dy == pnt[cnt-1].y); pnt[cnt].x = (short)dx; pnt[cnt].y = (short)dy; ++cnt; pnt[cnt].x = (short)dx2; pnt[cnt].y = (short)dy2; ++cnt; } } if (!cnt) return; } else { pnt[0].x = (short)point[gx][gy]->dx; pnt[0].y = (short)point[gx][gy]->dy; pnt[1].x = (short)point[gx+1][gy]->dx; pnt[1].y = (short)point[gx+1][gy]->dy; pnt[2].x = (short)point[gx+1][gy+1]->dx; pnt[2].y = (short)point[gx+1][gy+1]->dy; pnt[3].x = (short)point[gx][gy+1]->dx; pnt[3].y = (short)point[gx][gy+1]->dy; cnt = 4; } if (isVisible(pnt[0].x,pnt[0].y) || isVisible(pnt[1].x,pnt[1].y) || isVisible(pnt[2].x,pnt[2].y) || isVisible(pnt[3].x,pnt[3].y)) XFillPolygon(display,drw,gc,pnt,cnt,Complex,CoordModeOrigin); } /*** Draw a line in the window after doing transformations ***/ void drawLine(GC gc, double dx, double dy, double dz, double dx2, double dy2, double dz2) { double thick; if (!isVisible((int)dx,(int)dy) && !isVisible((int)dx2,(int)dy2)) return; if (do_clip && !clipLine(dx,dy,dz,dx2,dy2,dz2)) return; // Change line thickness depending on distance away thick = (double)thickness * scale * getPmult(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 dot ***/ void drawDot(GC gc, double dx, double dy, double dz) { double diam,radius; if (do_clip && dz < clipline) return; // Change head diameter depending on distance. Lots of X servers won't // fill if diam < 2. diam = thickness * scale * getPmult(dz); if (diam < 2) { if (isVisible((int)dx,(int)dy)) XDrawPoint(display,drw,gc,(int)dx,(int)dy); } else { radius = diam / 2; dx = dx - radius; dy = dy - radius; if (dx + diam >= 0 && dx <= width && dy + diam >= 0 && dy <= height) XFillArc( display,drw,gc, (int)dx,(int)dy,(int)diam,(int)diam,0,CIRCLE); } }