/***************************************************************************** JELLY: 3D Simulation of waves moving within a cube of jelly. Written by Neil Robertson Version: 20061003 Compiling: CC/c++ -L/usr/X11R6/lib -lX11 -lXext -lm Keys: P - Pause R - Reset Q - Quit A - Decrease perspective half distance Z - Increase perspective half distance S - Move towards cube X - Move away from cube D - Move Z clip into screen C - Move Z clip out of screen G - Increase colour adjust B - Decrease colour adjust 1 - Decrease colour cycling amount 2 - Increase colour cycling amount U - Rotate anticlockwise I - Stop rotating O - Rotate clockwise E - Toggle free edges F - Toggle flat algorithm on/off H - Toggle colour cycling on/off J - Toggle pull only on/off K - Flip kick direction L - Toggle clipping T - Iterate through draw types W - Toggle info on/off Y - Toggle perspective Arrow up - Move viewpoint up Arrow down - Move down Arrow left - Move left Arrow right - Move right Mouse: Button 1 - Rotate cube Button 2 & 3 - Kick cube *****************************************************************************/ #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 DEPTH 300 #define CUBE_X_SIZE 15 #define CUBE_Y_SIZE 15 #define CUBE_Z_SIZE 15 #define MASS 1000 #define INF 1000000000 #define KICK 5 #define KICK_WIDTH 5 #define KICK_LIST_LEN 8 #define NUM_DRAW_TYPES 6 #define NUM_COLOURS 90 #define NUM_STRESS_COLOURS 76 #define THICKNESS 1 #define COL_ADJUST 4 #define MAX_VEL 10 #define SCALE 0.8 #define SCALE_INC 0.01 #define HD_INC 10 #define CLIP_INC 10 #define VP_INC 3 #define DELAY 20000 /******************************** FORWARD DECL *******************************/ void parseCmdLine(int argc, char **argv); void Xinit(); void init(); void mainLoop(); void doKick(); void doXkick(int cx, int cy, int cz, double kick); void doYkick(int cx, int cy, int cz, double kick); void doZkick(int cx, int cy, int cz, double kick); void updateYrot(double ang); void fixEdges(); void drawScreen(); void resetAll(); void setSizes(); bool edgePoint(int cx, int cy, int cz); bool sidePoint(int cx, int cy, int cz); void drawLine( int col, double xfrom, double yfrom, double zfrom, double xto, double yto, double zto); int clipLine( double &xfrom, double &yfrom, double &zfrom, double &xto, double &yto, double &zto); double modColour(double col); double getPmult(double z); double hypot3(double x, double y, double z); double sgn(double d); /********************************* GLOBALS ***********************************/ Display *display; char *disp; Window win; XdbeSwapInfo swapinfo; Drawable drw; GC gcb,gcol[NUM_COLOURS]; int width; int height; int depth; int width2; int height2; int depth2; int cube_x_size; int cube_y_size; int cube_z_size; int max_cube_x; int max_cube_y; int max_cube_z; int x_viewpoint; int y_viewpoint; int half_dist; int kick_width; int draw_type; int num_points; int delay; int col_cyc_add; int thickness; bool use_dbe; bool do_perspective; bool paused; bool fix_edges; bool do_pntkick; bool do_clip; bool do_pull; bool do_flat; bool do_info; bool do_col_cyc; double orig_width; double orig_height; double rect_width; double rect_height; double info_width; double x_rot_ang; double y_rot_ang; double z_rot_ang; double x_seperation; double y_seperation; double z_seperation; double x_scale; double y_scale; double max_vel; double kick; double mass; double kick_maxhyp; double inv_friction; double clip_z; double avg_seperation; double col_adjust; double sinang[360]; double cosang[360]; enum { DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT, DIR_IN, DIR_OUT }; enum { DRAW_POINTS, DRAW_SIDE_POINTS, DRAW_LINES, DRAW_SIDE_LINES, DRAW_POLYS, DRAW_SIDE_POLYS }; enum { NOT_CLIPPED, CLIPPED, NOT_VISIBLE }; /******************************** POINT CLASS ********************************/ class cl_point { public: int cube_x; int cube_y; int cube_z; double x,y,z; double stx,sty,stz; double dx,dy,dz; double dist; double x_vel; double y_vel; double z_vel; double diam; double radius; cl_point(int cx, int cy, int cz): cube_x(cx), cube_y(cy), cube_z(cz) { reset(); } void reset(); void run(); void calcVelocity(); void calcPosition(); void project(); void draw(); void fillPolygon( int cx, int cy, int cz, int cx2, int cy2, int cz2, int cx3, int cy3, int cz3); } ****point,**sortpoint; /*** Reset to start configuration ***/ void cl_point::reset() { // Calculate x,y,z imagining centre point is in middle of // the window stx = x = (double)cube_x * x_seperation - width2; sty = y = (double)cube_y * y_seperation - height2; stz = z = (double)cube_z * z_seperation - depth2; x_vel = 0; y_vel = 0; z_vel = 0; diam = 0; radius = 0; } /*** Run the point ***/ void cl_point::run() { if (!paused) { calcVelocity(); calcPosition(); } project(); } /*** Calculate forces, velocity and position ***/ void cl_point::calcVelocity() { double xd,yd,zd; double xfrc,yfrc,zfrc; double mult; int cx,cy,cz; cx = cube_x + 1; cy = cube_y + 1; cz = cube_z + 1; if (cx < cube_x_size) { xd = point[cx][cube_y][cube_z]->x - x; yd = point[cx][cube_y][cube_z]->y - y; zd = point[cx][cube_y][cube_z]->z - z; yfrc = yd / mass; zfrc = zd / mass; if (!do_pull || fabs(xd) > x_seperation) { if (do_flat) xfrc = (xd - x_seperation) / mass; else { mult = fabs(xd) < x_seperation ? fabs(xd / x_seperation): 1; xfrc = (hypot3(xd,yd,zd) - x_seperation) * sgn(xd) * mult / mass; } } else xfrc = 0; if (!fix_edges || !edgePoint(cube_x,cube_y,cube_z)) { x_vel += xfrc; y_vel += yfrc; z_vel += zfrc; } if (!fix_edges || !edgePoint(cx,cube_y,cube_z)) { point[cx][cube_y][cube_z]->x_vel -= xfrc; point[cx][cube_y][cube_z]->y_vel -= yfrc; point[cx][cube_y][cube_z]->z_vel -= zfrc; } } if (cy < cube_y_size) { yd = point[cube_x][cy][cube_z]->y - y; xd = point[cube_x][cy][cube_z]->x - x; zd = point[cube_x][cy][cube_z]->z - z; xfrc = xd / mass; zfrc = zd / mass; if (!do_pull || fabs(yd) > y_seperation) { if (do_flat) yfrc = (yd - y_seperation) / mass; else { mult = fabs(yd) < y_seperation ? fabs(yd / y_seperation) : 1; yfrc = (hypot3(xd,yd,zd) - y_seperation) * sgn(yd) * mult / mass; } } else yfrc = 0; if (!fix_edges || !edgePoint(cube_x,cube_y,cube_z)) { z_vel += zfrc; y_vel += yfrc; x_vel += xfrc; } if (!fix_edges || !edgePoint(cube_x,cy,cube_z)) { point[cube_x][cy][cube_z]->x_vel -= xfrc; point[cube_x][cy][cube_z]->y_vel -= yfrc; point[cube_x][cy][cube_z]->z_vel -= zfrc; } } if (cz < cube_z_size) { zd = point[cube_x][cube_y][cz]->z - z; xd = point[cube_x][cube_y][cz]->x - x; yd = point[cube_x][cube_y][cz]->y - y; xfrc = xd / mass; yfrc = yd / mass; if (!do_pull || fabs(zd) > z_seperation) { if (do_flat) zfrc = (zd - z_seperation) / mass; else { mult = fabs(zd) < z_seperation ? fabs(zd / z_seperation) : 1; zfrc = (hypot3(xd,yd,zd) - z_seperation) * sgn(zd) * mult / mass; } } else zfrc = 0; if (!fix_edges || !edgePoint(cube_x,cube_y,cube_z)) { x_vel += xfrc; y_vel += yfrc; z_vel += zfrc; } if (!fix_edges || !edgePoint(cube_x,cube_y,cz)) { point[cube_x][cube_y][cz]->x_vel -= xfrc; point[cube_x][cube_y][cz]->y_vel -= yfrc; point[cube_x][cube_y][cz]->z_vel -= zfrc; } } // Stop things running out of control if (fabs(x_vel) > max_vel) x_vel = max_vel * sgn(x_vel); if (fabs(y_vel) > max_vel) y_vel = max_vel * sgn(y_vel); if (fabs(z_vel) > max_vel) z_vel = max_vel * sgn(z_vel); } /*** Now all the velocities calculated , work out the positions ***/ void cl_point::calcPosition() { x_vel *= inv_friction; y_vel *= inv_friction; z_vel *= inv_friction; x += x_vel; y += y_vel; z += z_vel; dist = hypot3(x - stx, y - sty, z - stz); } /*** Calculate the points x,y,z co-ordinates on screen from its cube location and current rotation angles ***/ void cl_point::project() { double xt,yt; double pmult; dx = x; dy = y; dz = z; // Rotate about Z axis xt = dx; dx = dx * cosang[(int)z_rot_ang] + dy * sinang[(int)z_rot_ang]; dy = dy * cosang[(int)z_rot_ang] - xt * sinang[(int)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]; pmult = getPmult(dz); dx = dx * x_scale * pmult + width2 + x_viewpoint; dy = dy * y_scale * pmult + height2 + y_viewpoint; } /*** Draw the point ***/ void cl_point::draw() { int cx,cy,cz; double col; bool draw_all; switch(draw_type) { case DRAW_SIDE_POINTS: if (!sidePoint(cube_x,cube_y,cube_z)) return; // Fall through case DRAW_POINTS: if (do_clip && dz < clip_z) return; diam = thickness * x_scale * getPmult(dz); if (diam < 2) diam = 2; radius = diam / 2; if (dx + diam >= 0 && dx <= width && dy + diam >= 0 && dy <= height) { col = modColour((dist / avg_seperation) * col_adjust); XFillArc( display, drw, gcol[(int)col], (int)(dx-radius),(int)(dy-radius), (int)diam,(int)diam,0,CIRCLE); } break; case DRAW_SIDE_LINES: if (!sidePoint(cube_x,cube_y,cube_z)) return; // Fall through case DRAW_LINES: draw_all = (draw_type == DRAW_LINES); cx = cube_x + 1; cy = cube_y + 1; cz = cube_z + 1; if (cx < cube_x_size && (draw_all || sidePoint(cx,cube_y,cube_z))) { col = modColour( ((dist + point[cx][cube_y][cube_z]->dist) / 2 / avg_seperation) * col_adjust); drawLine( (int)col, dx,dy,dz, point[cx][cube_y][cube_z]->dx, point[cx][cube_y][cube_z]->dy, point[cx][cube_y][cube_z]->dz); } if (cy < cube_y_size && (draw_all || sidePoint(cube_x,cy,cube_z))) { col = modColour( ((dist + point[cube_x][cy][cube_z]->dist) / 2 / avg_seperation) * col_adjust); drawLine( (int)col, dx,dy,dz, point[cube_x][cy][cube_z]->dx, point[cube_x][cy][cube_z]->dy, point[cube_x][cy][cube_z]->dz); } if (cz < cube_z_size && (draw_all || sidePoint(cube_x,cube_y,cz))) { col = modColour( ((dist + point[cube_x][cube_y][cz]->dist) / 2 / avg_seperation) * col_adjust); drawLine( (int)col, dx,dy,dz, point[cube_x][cube_y][cz]->dx, point[cube_x][cube_y][cz]->dy, point[cube_x][cube_y][cz]->dz); } break; case DRAW_POLYS: case DRAW_SIDE_POLYS: if (draw_type == DRAW_POLYS || !cube_x || cube_x == max_cube_x) fillPolygon( cube_x,cube_y + 1,cube_z, cube_x,cube_y + 1,cube_z + 1, cube_x,cube_y,cube_z + 1); if (draw_type == DRAW_POLYS || !cube_y || cube_y == max_cube_y) fillPolygon( cube_x + 1,cube_y,cube_z, cube_x + 1,cube_y,cube_z + 1, cube_x,cube_y,cube_z + 1); if (draw_type == DRAW_POLYS || !cube_z || cube_z == max_cube_z) fillPolygon( cube_x + 1,cube_y,cube_z, cube_x + 1,cube_y + 1,cube_z, cube_x,cube_y + 1,cube_z); } } /*** Fill a polygon ***/ void cl_point::fillPolygon( int cx, int cy, int cz, int cx2, int cy2, int cz2, int cx3, int cy3, int cz3) { XPoint pnt[6]; struct { double x1,y1,z1; double x2,y2,z2; } lines[5]; double col; int cnt,i; if (cx > max_cube_x || cx2 > max_cube_x || cx3 > max_cube_x || cy > max_cube_y || cy2 > max_cube_y || cy3 > max_cube_y || cz > max_cube_z || cz2 > max_cube_z || cz3 > max_cube_z) return; col = modColour((dist + point[cx][cy][cz]->dist + point[cx2][cy2][cz2]->dist + point[cx3][cy3][cz3]->dist) / 4 / avg_seperation * col_adjust); if (!do_clip) { pnt[0].x = (short int)dx; pnt[0].y = (short int)dy; pnt[1].x = (short int)point[cx][cy][cz]->dx; pnt[1].y = (short int)point[cx][cy][cz]->dy; pnt[2].x = (short int)point[cx2][cy2][cz2]->dx; pnt[2].y = (short int)point[cx2][cy2][cz2]->dy; pnt[3].x = (short int)point[cx3][cy3][cz3]->dx; pnt[3].y = (short int)point[cx3][cy3][cz3]->dy; XFillPolygon( display,drw,gcol[(int)col], pnt,4,Complex,CoordModeOrigin); return; } // Use intermediate variables since don't want dx,dy,dz etc // clipped because they'll be used again for the next point lines[0].x1 = dx; lines[0].y1 = dy; lines[0].z1 = dz; lines[0].x2 = point[cx][cy][cz]->dx; lines[0].y2 = point[cx][cy][cz]->dy; lines[0].z2 = point[cx][cy][cz]->dz; lines[1].x1 = point[cx][cy][cz]->dx; lines[1].y1 = point[cx][cy][cz]->dy; lines[1].z1 = point[cx][cy][cz]->dz; lines[1].x2 = point[cx2][cy2][cz2]->dx; lines[1].y2 = point[cx2][cy2][cz2]->dy; lines[1].z2 = point[cx2][cy2][cz2]->dz; lines[2].x1 = point[cx2][cy2][cz2]->dx; lines[2].y1 = point[cx2][cy2][cz2]->dy; lines[2].z1 = point[cx2][cy2][cz2]->dz; lines[2].x2 = point[cx3][cy3][cz3]->dx; lines[2].y2 = point[cx3][cy3][cz3]->dy; lines[2].z2 = point[cx3][cy3][cz3]->dz; lines[3].x1 = point[cx3][cy3][cz3]->dx; lines[3].y1 = point[cx3][cy3][cz3]->dy; lines[3].z1 = point[cx3][cy3][cz3]->dz; lines[3].x2 = dx; lines[3].y2 = dy; lines[3].z2 = dz; // Iterate through the 4 lines for(i=0,cnt=0;i < 4;++i) { if (clipLine( lines[i].x1,lines[i].y1,lines[i].z1, lines[i].x2,lines[i].y2,lines[i].z2) == NOT_VISIBLE) continue; // Only increment to next position if new point is different // to last point (because line was clipped) cnt+= (cnt && ((short)lines[i].x1 != pnt[cnt].x || (short)lines[i].y1 != pnt[cnt].y)); pnt[cnt].x = (short)lines[i].x1; pnt[cnt].y = (short)lines[i].y1; ++cnt; pnt[cnt].x = (short)lines[i].x2; pnt[cnt].y = (short)lines[i].y2; } if (cnt) XFillPolygon( display,drw,gcol[(int)col], pnt,cnt+1,Complex,CoordModeOrigin); } /********************************** START ***********************************/ int main(int argc, char **argv) { parseCmdLine(argc,argv); Xinit(); init(); mainLoop(); return 0; } /*** Parse whats on the command line ***/ void parseCmdLine(int argc, char **argv) { const char *opt[] = { "dp", "w", "h", "d", "cx", "cy", "cz", "kick", "kwidth", "depkick", "mass", "thk", "maxvel", "fric", "cadj", "draw", "delay", "flat", "pull", "colcyc", "info", "dbe" }; enum { OPT_DP, OPT_W, OPT_H, OPT_D, OPT_CX, OPT_CY, OPT_CZ, OPT_KICK, OPT_KWIDTH, OPT_DEPKICK, OPT_MASS, OPT_THK, OPT_MAXVEL, OPT_FRIC, OPT_CADJ, OPT_DRAW, OPT_DELAY, OPT_FLAT, OPT_PULL, OPT_COLCYC, OPT_INFO, OPT_DBE, OPT_END }; int i,o; double val; disp = NULL; width = WIDTH; height = HEIGHT; depth = DEPTH; cube_x_size = CUBE_X_SIZE; cube_y_size = CUBE_Y_SIZE; cube_z_size = CUBE_Z_SIZE; kick = KICK; kick_width = KICK_WIDTH; mass = MASS; inv_friction = 1; col_adjust = COL_ADJUST; draw_type = DRAW_POINTS; max_vel = MAX_VEL; thickness = THICKNESS; delay = DELAY; do_pntkick = true; use_dbe = false; do_perspective = true; do_flat = false; do_pull = false; do_info = false; do_col_cyc = 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_DEPKICK: case OPT_FLAT: case OPT_PULL: case OPT_COLCYC: case OPT_INFO: case OPT_DBE: --i; break; default: if (i == argc-1) goto USAGE; val = atof(argv[i+1]); } switch(o) { case OPT_DP: disp = argv[i+1]; break; case OPT_W: if (val < 1) { puts("ERROR: Width must be > 0."); exit(1); } width = (int)val; break; case OPT_H: if (val < 1) { puts("ERROR: Height must be > 0."); exit(1); } height = (int)val; break; case OPT_D: if (val < 1) { puts("ERROR: Depth must be > 0."); exit(1); } depth = (int)val; break; case OPT_CX: if (val < 2) { puts("ERROR: Cube X must be > 1."); exit(1); } cube_x_size = (int)val; break; case OPT_CY: if (val < 2) { puts("ERROR: Cube Y must be > 1."); exit(1); } cube_y_size = (int)val; break; case OPT_CZ: if (val < 2) { puts("ERROR: Cube Z must be > 1."); exit(1); } cube_z_size = (int)val; break; case OPT_KICK: kick = val; break; case OPT_KWIDTH: if (val < 1) { puts("ERROR: Kick width must be > 0."); exit(1); } kick_width = (int)val; break; case OPT_MASS: mass = val; break; case OPT_THK: if (val < 1) { puts("ERROR: Thickness must be > 0."); exit(1); } thickness = (int)val; break; case OPT_MAXVEL: if (val <= 0) { puts("ERROR: Max velocity must be > 0."); exit(1); } max_vel = val; break; case OPT_FRIC: if (val < 0 || val > 1) { puts("ERROR: Friction must be from 0 to 1."); exit(1); } inv_friction = 1 - val; break; case OPT_CADJ: if (val < 1) { puts("ERROR: Colour adjust must be > 0."); exit(1); } col_adjust = val; break; case OPT_DRAW: if (val < 1 || val > NUM_DRAW_TYPES) { printf("ERROR: Draw type must be between 1 and %d\n",NUM_DRAW_TYPES); exit(1); } draw_type = (int)val - 1; break; case OPT_DELAY: if (val < 0) { puts("ERROR: Delay must be >= 0."); exit(1); } delay = (int)val; break; case OPT_DEPKICK: do_pntkick = false; break; case OPT_FLAT: do_flat = true; break; case OPT_PULL: do_pull = true; break; case OPT_COLCYC: do_col_cyc = true; break; case OPT_INFO: do_info = true; break; case OPT_DBE: use_dbe = true; break; default: goto USAGE; } } return; USAGE: printf("Usage: %s\n" " -dp \n" " -w : Default = %d\n" " -h : Default = %d\n" " -d : Default = %d\n" " -cx : Default = %d\n" " -cy : Default = %d\n" " -cz : Default = %d\n" " -kick : Default = %d\n" " -kwidth : Default = %d\n" " -mass : Default = %d\n" " -thk : Default = %d\n" " -maxvel : Default = %d\n" " -fric 1> : Default = 0\n" " -cadj : Default = %d\n" " -draw : Default = 1\n" " -delay : Default = %d\n" " -depkick : Do depression kick\n" " -flat : Use 'flat' algorithm\n" " -pull : Only pull points, don't push\n" " -colcyc : Do colour cycling\n" " -info : Show flag info squares\n" " -dbe : Use double buffering.\n" "All arguments are optional.\n", argv[0], WIDTH, HEIGHT, DEPTH, CUBE_X_SIZE, CUBE_Y_SIZE, CUBE_Z_SIZE, KICK, KICK_WIDTH, MASS, THICKNESS, MAX_VEL, COL_ADJUST, DELAY); exit(1); } /*** Set up X ***/ void Xinit() { Window rootwin; XGCValues gcvals; XEvent event; Colormap cmap; XColor col; XColor unused; XTextProperty title; int screen,white,black; int cnt,stage; unsigned char r,g,b; char colstr[5]; char *tstr = "JELLY"; 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; // Set title XStringListToTextProperty(&tstr,1,&title); XSetWMProperties(display,win,&title,NULL,NULL,0,NULL,NULL,NULL); // Create black GC gcvals.foreground = black; gcb = XCreateGC(display,win,GCForeground,&gcvals); // Create colour GCs r = 0; g = 0; b = 0xF; 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.line_width = thickness; gcvals.foreground = col.pixel; gcol[cnt] = XCreateGC( display,win,GCForeground | GCLineWidth,&gcvals); switch(stage) { case 1: if (++g == 0xF) stage = 2; break; case 2: if (!--b) stage = 3; break; case 3: if (++r == 0xF) stage = 4; break; case 4: if (!--g) stage = 5; break; case 5: if (++b == 0xF) stage = 6; break; case 6: // Colours here are used purely for smooth colour // cycling as they go from red back to blue. Not used // as a stress colour. --r; } } XSelectInput( display,win, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); XMapWindow(display,win); do { XNextEvent(display,&event); } while(event.type != Expose); } /*** Create the cube ***/ void init() { int cx,cy,cz,pos; double ang; setSizes(); depth2 = depth / 2; orig_width = (double)width; orig_height = (double)height; max_cube_x = cube_x_size - 1; max_cube_y = cube_y_size - 1; max_cube_z = cube_z_size - 1; x_seperation = (double)width / max_cube_x; y_seperation = (double)height / max_cube_y; z_seperation = (double)depth / max_cube_z; avg_seperation = (x_seperation + y_seperation + z_seperation) / 3; x_scale = y_scale = SCALE; fix_edges = true; do_clip = false; clip_z = 0; half_dist = depth * 3; kick_maxhyp = hypot(kick_width,kick_width); col_cyc_add = 0; // Set up cube points num_points = cube_x_size * cube_y_size * cube_z_size; sortpoint = new cl_point*[num_points]; point = new cl_point***[cube_x_size]; for(cx=0,pos=0;cx < cube_x_size;++cx) { point[cx] = new cl_point**[cube_y_size]; for(cy=0;cy < cube_y_size;++cy) { point[cx][cy] = new cl_point*[cube_z_size]; for(cz=0;cz < cube_z_size;++cz,++pos) { point[cx][cy][cz] = new cl_point(cx,cy,cz); sortpoint[pos] = point[cx][cy][cz]; } } } // Set up trig tables for(ang=0;ang < 360;++ang) { sinang[(int)ang] = sin(ang / DEGS_PER_RADIAN); cosang[(int)ang] = cos(ang / DEGS_PER_RADIAN); } } /*** Set up some widths and heights ***/ void setSizes() { width2 = width / 2; height2 = height / 2; depth2 = depth / 2; rect_width = width / 8; rect_height = height / 20; info_width = rect_width / 3; } /*********************************** RUNTIME *********************************/ /*** Do all the important stuff ***/ void mainLoop() { Window rw,cw; XEvent event; KeySym ksym; double y_ang_add; bool rotate; char key; int rx,ry; u_int mask; int ptr_x; int ptr_y; int last_ptr_x; int last_ptr_y; last_ptr_x = -1; last_ptr_y = -1; x_rot_ang = 0; y_rot_ang = 0; z_rot_ang = 0; y_ang_add = 0; for(rotate=false,paused=false;;) { // Get rid of any expose events XCheckTypedEvent(display,Expose,&event); // Check for key press if (XCheckTypedEvent(display,KeyPress,&event)) { XLookupString(&event.xkey,&key,1,&ksym,NULL); switch(ksym) { case XK_a: case XK_A: half_dist -= HD_INC; break; case XK_z: case XK_Z: half_dist += HD_INC; break; case XK_c: case XK_C: clip_z -= CLIP_INC; break; case XK_d: case XK_D: clip_z += CLIP_INC; break; case XK_s: case XK_S: x_scale += SCALE_INC; y_scale += SCALE_INC; break; case XK_x: case XK_X: x_scale -= SCALE_INC; y_scale -= SCALE_INC; break; case XK_g: case XK_G: col_adjust++; break; case XK_b: case XK_B: if (col_adjust > 1) col_adjust--; 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_i: case XK_I: y_ang_add = 0; break; case XK_o: case XK_O: y_ang_add = -1; break; case XK_u: case XK_U: y_ang_add = 1; break; case XK_e: case XK_E: fixEdges(); break; case XK_f: case XK_F: do_flat = !do_flat; break; case XK_h: case XK_H: do_col_cyc = !do_col_cyc; break; case XK_j: case XK_J: do_pull = !do_pull; break; case XK_k: case XK_K: kick = -kick; break; case XK_l: case XK_L: do_clip = !do_clip; break; case XK_p: case XK_P: paused = !paused; break; case XK_q: case XK_Q: exit(0); case XK_r: case XK_R: resetAll(); break; case XK_t: case XK_T: draw_type = (draw_type + 1) % NUM_DRAW_TYPES; break; case XK_w: case XK_W: do_info = !do_info; break; case XK_y: case XK_Y: do_perspective = !do_perspective; 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; } } // Check for button press if (XCheckTypedEvent(display,ButtonPress,&event)) { switch(event.xbutton.button) { case 1: rotate = true; break; default: doKick(); } } // Check for button release if (XCheckTypedEvent(display,ButtonRelease,&event) && event.xbutton.button == 1) { rotate = false; last_ptr_x = -1; last_ptr_y = -1; } // Check for window resize if (XCheckTypedEvent(display,ConfigureNotify,&event)) { // Update dimensions and also reset scale so cube // always is same size relative to the window width = event.xconfigure.width; height = event.xconfigure.height; setSizes(); x_scale = SCALE * (double)event.xconfigure.width / orig_width; y_scale = SCALE * (double)event.xconfigure.height / orig_height; } // Check for mouse move if (rotate) { XQueryPointer( display, win,&rw,&cw,&rx,&ry,&ptr_x,&ptr_y,&mask); if (last_ptr_x != -1) { x_rot_ang += (double)(ptr_y - last_ptr_y) / 5; if (x_rot_ang >= 360) x_rot_ang -= 360; else if (x_rot_ang < 0) x_rot_ang += 360; updateYrot((double)(last_ptr_x - ptr_x) / 5); } last_ptr_x = ptr_x; last_ptr_y = ptr_y; } // Add on keyboard rotation updateYrot(y_ang_add); // Draw the jelly and the info drawScreen(); } } /*** Kick the nearest point on a side to the position of the mouse pointer ***/ void doKick() { Window rw,cw; int rx,ry; u_int mask; int ptr_x; int ptr_y; int cx,cy,cz; int i,pos; int ncx[KICK_LIST_LEN]; int ncy[KICK_LIST_LEN]; int ncz[KICK_LIST_LEN]; double ndist_xy[KICK_LIST_LEN]; double min_z; double min_dist_xy; double dist_xy; double dist_xy2; XQueryPointer(display,win,&rw,&cw,&rx,&ry,&ptr_x,&ptr_y,&mask); // Search through for nearest matching 8 dx,dy then sort based on // dz. Can't just use the x,y closest as it might be on the wrong side // of the cube to which we want to kick. min_dist_xy = x_seperation < y_seperation ? x_seperation : y_seperation; memset(ncx,-1,sizeof(ncx)); for(cx=0,pos=0;cx < cube_x_size;++cx) { for(cy=0;cy < cube_y_size;++cy) { for(cz=0;cz < cube_z_size;++cz) { if (!sidePoint(cx,cy,cz) || (fix_edges && edgePoint(cx,cy,cz))) continue; dist_xy = fabs(point[cx][cy][cz]->dx - ptr_x) + fabs(point[cx][cy][cz]->dy - ptr_y); if (dist_xy >= min_dist_xy) continue; // First see if we have a free slot for(i=0;i < KICK_LIST_LEN;++i) { if (ncx[i] == -1) { ncx[i] = cx; ncy[i] = cy; ncz[i] = cz; ndist_xy[i] = dist_xy; break; } } if (i < KICK_LIST_LEN) continue; // No free slots so find hightest min dist // and overwrite it if we're lower dist_xy2 = -INF; for(i=0,pos=-1;i < KICK_LIST_LEN;++i) if (ndist_xy[i] > dist_xy2) pos = i; if (dist_xy < ndist_xy[i]) { ncx[pos] = cx; ncy[pos] = cy; ncz[pos] = cz; ndist_xy[pos] = dist_xy; } } } } // If -1 then got nothing if (ncx[0] == -1) return; // Find min Z out of the 8 (if there are 8) min_z = point[ncx[0]][ncy[0]][ncz[0]]->dz; for(i=1,pos=0;i < KICK_LIST_LEN && ncx[i] != -1;++i) { if (point[ncx[i]][ncy[i]][ncz[i]]->dz < min_z) { min_z = point[ncx[i]][ncy[i]][ncz[i]]->dz; pos = i; } } // Added kick to appropriate axis if (!ncx[pos]) doXkick(0,ncy[pos],ncz[pos],kick); else if (ncx[pos] == max_cube_x) doXkick(ncx[pos],ncy[pos],ncz[pos],-kick); else if (!ncy[pos]) doYkick(ncx[pos],0,ncz[pos],kick); else if (ncy[pos] == max_cube_y) doYkick(ncx[pos],ncy[pos],ncz[pos],-kick); else if (!ncz[pos]) doZkick(ncx[pos],ncy[pos],0,kick); else if (ncz[pos] == max_cube_z) doZkick(ncx[pos],ncy[pos],ncz[pos],-kick); } /*** Do an X velocity kick ***/ void doXkick(int cx, int cy, int cz, double kick) { int yfrom,yto; int zfrom,zto; int y,z; double hyp; double pkick; if (do_pntkick) { point[cx][cy][cz]->x_vel += kick; return; } yfrom = cy - kick_width; if (yfrom < 0) yfrom = 0; yto = cy + kick_width; if (yto > max_cube_y) yto = max_cube_y; zfrom = cz - kick_width; if (zfrom < 0) zfrom = 0; zto = cz + kick_width; if (zto > max_cube_z) zto = max_cube_z; for(y = yfrom;y <= yto;++y) { if (fix_edges && (y <= 0 || y >= max_cube_y)) continue; for(z = zfrom;z <= zto;++z) { if (fix_edges && (z <= 0 || z >= max_cube_z)) continue; hyp = hypot(cy - y, cz - z); pkick = kick - kick * (hyp / kick_maxhyp); point[cx][y][z]->x_vel += pkick; } } } /*** Do a Y velocity kick ***/ void doYkick(int cx, int cy, int cz, double kick) { int xfrom,xto; int zfrom,zto; int x,z; double hyp; double pkick; if (do_pntkick) { point[cx][cy][cz]->y_vel += kick; return; } xfrom = cx - kick_width; if (xfrom < 0) xfrom = 0; xto = cx + kick_width; if (xto > max_cube_x) xto = max_cube_x; zfrom = cz - kick_width; if (zfrom < 0) zfrom = 0; zto = cz + kick_width; if (zto > max_cube_z) zto = max_cube_z; for(x = xfrom;x <= xto;++x) { if (fix_edges && (x <= 0 || x >= max_cube_x)) continue; for(z = zfrom;z <= zto;++z) { if (fix_edges && (z <= 0 || z >= max_cube_z)) continue; hyp = hypot(cx - x, cz - z); pkick = kick - kick * (hyp / kick_maxhyp); point[x][cy][z]->y_vel += pkick; } } } /*** Do a Z velocity kick ***/ void doZkick(int cx, int cy, int cz, double kick) { int xfrom,xto; int yfrom,yto; int x,y; double hyp; double pkick; if (do_pntkick) { point[cx][cy][cz]->z_vel += kick; return; } xfrom = cx - kick_width; if (xfrom < 0) xfrom = 0; xto = cx + kick_width; if (xto > max_cube_x) xto = max_cube_x; yfrom = cy - kick_width; if (yfrom < 0) yfrom = 0; yto = cy + kick_width; if (yto > max_cube_y) yto = max_cube_y; for(x = xfrom;x <= xto;++x) { if (fix_edges && (x <= 0 || x >= max_cube_x)) continue; for(y = yfrom;y <= yto;++y) { if (fix_edges && (y <= 0 || y >= max_cube_y)) continue; hyp = hypot(cx - x, cy - y); pkick = kick - kick * (hyp / kick_maxhyp); point[x][y][cz]->z_vel += pkick; } } } /*** Update the Y rotation ***/ void updateYrot(double ang) { y_rot_ang += ang; if (y_rot_ang >= 360) y_rot_ang -= 360; else if (y_rot_ang < 0) y_rot_ang += 360; } /*** Fix or unfix edges ***/ void fixEdges() { int cx,cy,cz; if (fix_edges) { fix_edges = false; return; } // Pull all the edges back to where they should be for(cx=0;cx < cube_x_size;++cx) { for(cy=0;cy < cube_y_size;++cy) { for(cz=0;cz < cube_z_size;++cz) { if (edgePoint(cx,cy,cz)) point[cx][cy][cz]->reset(); } } } fix_edges = true; } /*** Draw the jelly and the info display ***/ void drawScreen() { cl_point *max_z; int i,j; // Run points to get new z positions for(i=0;i < num_points;++i) sortpoint[i]->run(); // Do insertion sort on Z position. Couldn't get qsort() to work. for(i=1;i < num_points;++i) { max_z = sortpoint[i]; for(j=i;j && sortpoint[j-1]->dz < max_z->dz;--j) sortpoint[j] = sortpoint[j-1]; sortpoint[j] = max_z; } // Draw the jelly for(i=0;i < num_points;++i) sortpoint[i]->draw(); // Draw the info squares if (do_info) { XFillRectangle( display,drw,gcb,0,0,(int)rect_width,(int)rect_height); if (fix_edges) XFillRectangle( display,drw,gcol[10], 0,0,(int)info_width,(int)rect_height); if (do_flat) XFillRectangle( display,drw,gcol[30], (int)info_width,0, (int)info_width,(int)rect_height); if (do_pull) XFillRectangle( display,drw,gcol[54], (int)info_width*2,0, (int)info_width,(int)rect_height); } // Update window if (use_dbe) XdbeSwapBuffers(display,&swapinfo,1); else XFlush(display); usleep(delay); if (!use_dbe) XClearWindow(display,drw); if (do_col_cyc) col_cyc_add = (col_cyc_add + 1) % NUM_COLOURS; } /*** Reset everything ***/ void resetAll() { int cx,cy,cz; // In case window size has changed x_seperation = (double)width / max_cube_x; y_seperation = (double)height / max_cube_y; z_seperation = (double)depth / max_cube_z; avg_seperation = (x_seperation + y_seperation + z_seperation) / 3; x_scale = y_scale = SCALE; x_rot_ang = 0; y_rot_ang = 0; z_rot_ang = 0; x_viewpoint = 0; y_viewpoint = 0; col_cyc_add = 0; for(cx=0;cx < cube_x_size;++cx) { for(cy=0;cy < cube_y_size;++cy) { for(cz=0;cz < cube_z_size;++cz) point[cx][cy][cz]->reset(); } } } /***************************** SUPPORT FUNCTIONS **************************/ /*** Returns true if point is on an edge ***/ bool edgePoint(int cx, int cy, int cz) { return (!cx && !cy) || (!cx && !cz) || (!cy && !cz) || (!cx && cy == max_cube_y) || (!cx && cz == max_cube_z) || (!cy && cx == max_cube_x) || (!cy && cz == max_cube_z) || (!cz && cx == max_cube_x) || (!cz && cy == max_cube_y) || (cx == max_cube_x && cy == max_cube_y) || (cx == max_cube_x && cz == max_cube_z) || (cy == max_cube_x && cz == max_cube_z); } /*** Returns true if point is on a side of the cube ***/ bool sidePoint(int cx, int cy, int cz) { return (!cx || !cy || !cz || cx == max_cube_x || cy == max_cube_y || cz == max_cube_z); } /*** Draw a line ***/ void drawLine( int col, double xfrom, double yfrom, double zfrom, double xto, double yto, double zto) { double thick; GC gc; if (do_clip && clipLine(xfrom,yfrom,zfrom,xto,yto,zto) == NOT_VISIBLE) return; gc = gcol[col]; thick = (double)thickness * x_scale * getPmult(zfrom); XSetLineAttributes( display,gc, thick < 1 ? 1 : (int)thick, LineSolid,CapRound,JoinRound); XDrawLine(display,drw,gc,(int)xfrom,(int)yfrom,(int)xto,(int)yto); } /*** Clip a line ***/ int clipLine( double &xfrom, double &yfrom, double &zfrom, double &xto, double &yto, double &zto) { double ratio; int clipped; clipped = NOT_CLIPPED; if (zfrom < clip_z) { if (zto < clip_z) return NOT_VISIBLE; ratio = (clip_z - zfrom) / (zto - zfrom); xfrom += (xto - xfrom) * ratio; yfrom += (yto - yfrom) * ratio; clipped = CLIPPED; } else if (zto < clip_z) { if (zfrom < clip_z) return NOT_VISIBLE; ratio = (clip_z - zto) / (zfrom - zto); xto += (xfrom - xto) * ratio; yto += (yfrom - yto) * ratio; clipped = CLIPPED; } return clipped; } /*** Modify the colour ***/ double modColour(double col) { if (col > NUM_STRESS_COLOURS) col = NUM_STRESS_COLOURS - 1; return (double)((int)(col + col_cyc_add) % NUM_COLOURS); } /*** Get perspective multipler ***/ double getPmult(double z) { return do_perspective ? 1 / (pow(2,z / half_dist)) : 1; } /*** Get 3D hypotenuse ***/ double hypot3(double x, double y, double z) { return sqrt(x*x + y*y + z*z); } /*** Return the sign of a number ***/ double sgn(double d) { return (d > 0 ? 1 : (d < 0 ? -1 : 0)); }