/*********************************************************************************/ /* */ /* Animation of particles in billiard */ /* */ /* N. Berglund, december 2012, april 2021 */ /* UPDATE 14 April 21 : graphics files go to subfolder, */ /* Switch MOVIE to decide whether to create a movie */ /* UPDATE 3 May 21 : new domains */ /* */ /* Feel free to reuse, but if doing so it would be nice to drop a */ /* line to nils.berglund@univ-orleans.fr - Thanks! */ /* */ /* compile with */ /* gcc -o particle_billiard particle_billiard.c */ /* -O3 -L/usr/X11R6/lib -ltiff -lm -lGL -lGLU -lX11 -lXmu -lglut */ /* */ /* OMP acceleration may be more effective after executing, e.g., */ /* export OMP_NUM_THREADS=2 in the shell before running the program */ /* */ /* To make a video, set MOVIE to 1 and create subfolder tif_part */ /* It may be possible to increase parameter PAUSE */ /* */ /* create movie using */ /* ffmpeg -i part.%05d.tif -vcodec libx264 part.mp4 */ /* */ /*********************************************************************************/ #include #include #include #include #include #include #include /* Sam Leffler's libtiff library. */ #include #define MOVIE 0 /* set to 1 to generate movie */ #define WINWIDTH 1760 /* window width */ #define WINHEIGHT 990 /* window height */ #define SAVE_MEMORY 1 /* set to 1 to save memory when writing tiff images */ #define NO_EXTRA_BUFFER_SWAP 1 /* some OS require one less buffer swap when recording images */ #define XMIN -1.2 #define XMAX 2.8 /* x interval */ #define YMIN -1.125 #define YMAX 1.125 /* y interval for 9/16 aspect ratio */ #define SCALING_FACTOR 1.0 /* scaling factor of drawing, needed for flower billiards, otherwise set to 1.0 */ /* Choice of the billiard table, see global_particles.c */ #define B_DOMAIN 30 /* choice of domain shape */ #define CIRCLE_PATTERN 1 /* pattern of circles */ #define POLYLINE_PATTERN 8 /* pattern of polyline */ #define ABSORBING_CIRCLES 1 /* set to 1 for circular scatterers to be absorbing */ #define NABSCIRCLES 10 /* number of absorbing circles */ #define NMAXCIRCLES 50000 /* total number of circles (must be at least NCX*NCY for square grid) */ #define NMAXPOLY 100004 /* total number of sides of polygonal line */ #define NCX 9 /* number of circles in x direction */ #define NCY 20 /* number of circles in y direction */ #define NPOISSON 500 /* number of points for Poisson C_RAND_POISSON arrangement */ #define NGOLDENSPIRAL 2000 /* max number of points for C_GOLDEN_SPIRAL arrandement */ #define SDEPTH 2 /* Sierpinski gastket depth */ #define LAMBDA 1.0 /* parameter controlling shape of domain */ #define MU 0.0075 /* second parameter controlling shape of billiard */ #define FOCI 1 /* set to 1 to draw focal points of ellipse */ #define NPOLY 5 /* number of sides of polygon */ #define APOLY 0.2 /* angle by which to turn polygon, in units of Pi/2 */ #define LAMBDA_B 1.0 /* parameter controlling shape of domain (for P_POLYRING) */ #define NPOLY_B 100000 /* number of sides of second polygon */ #define APOLY_B 1.0 /* angle by which to turn second polygon, in units of Pi/2 */ #define DRAW_BILLIARD 1 /* set to 1 to draw billiard */ #define DRAW_CONSTRUCTION_LINES 0 /* set to 1 to draw additional construction lines for billiard */ #define PERIODIC_BC 0 /* set to 1 to enforce periodic boundary conditions when drawing particles */ #define PENROSE_RATIO 2.5 /* parameter controlling the shape of small ellipses in Penrose room */ #define RESAMPLE 0 /* set to 1 if particles should be added when dispersion too large */ #define DEBUG 0 /* draw trajectories, for debugging purposes */ /* Simulation parameters */ #define NPART 1 /* number of particles */ #define NPARTMAX 100000 /* maximal number of particles after resampling */ #define TRAJ_LENGTH 120 /* length of trajectory */ #define PLOT_NMAX 100 /* max length on length plot */ #define LMAX 0.01 /* minimal segment length triggering resampling */ #define DMIN 0.02 /* minimal distance to boundary for triggering resampling */ #define CYCLE 0 /* set to 1 for closed curve (start in all directions) */ #define SHOWTRAILS 0 /* set to 1 to keep trails of the particles */ #define HEATMAP 0 /* set to 1 to show heat map of particles */ #define PLOT_HEATMAP_AVERAGE 0 /* set to 1 to plot average number of particles in heat map */ #define SHOWZOOM 0 /* set to 1 to show zoom on specific area */ #define TEST_ACTIVE 0 /* set to 1 to test whether particle is in billiard */ #define PRINT_TRAJECTORY_LENGTH 1 /* set to 1 to print length of trajectory 0 */ #define PRINT_TRAJECTORY_ANGLE 1 /* set to 1 to print angle of trajectory 0 */ #define PRINT_TRAJECTORY_SLOPE 0 /* set to 1 to print slope of trajectory 0 */ #define PRINT_TRAJECTORY_PERIOD 0 /* set to 1 to print period of trajectory 0 */ #define DRAW_LENGTHS_PLOT 1 /* set to 1 to plot trajectory lengths */ #define LENGTHS_LOG_SCALE 0 /* set to 1 to use log scale for plot of lengths */ #define LENGTH_PLOT_POLAR 0 /* set to 1 to plot lengths in polar coordinates */ #define MIN_ANGLE 0.0 /* range of angles of trajectory */ #define MAX_ANGLE 135.0 /* range of angles of trajectory */ #define TEST_INITIAL_COND 0 /* set to 1 to allow only initial conditions that pass a test */ #define SLOW_AT_LONG_TRAJ 0 /* set to 1 to slow down movie for long trajectories */ #define ADD_SUCCESS_GALLERY 0 /* set to 1 to add gallery of successful trajectories at end of movie */ #define SUCCESS_GALLERY_FRAMES 25 /* number of frames per success */ #define EXIT_BOTH_WAYS 1 /* set to 1 to add exits to he left to succesful trajectories */ #define NSTEPS 6400 /* number of frames of movie */ #define TIME 2500 /* time between movie frames, for fluidity of real-time simulation */ #define DPHI 0.00001 /* integration step */ #define NVID 150 /* number of iterations between images displayed on screen */ /* Decreasing TIME accelerates the animation and the movie */ /* For constant speed of movie, TIME*DPHI should be kept constant */ /* However, increasing DPHI too much deterioriates quality of simulation */ /* NVID tells how often a picture is drawn in the animation, increase it for faster anim */ /* For a good quality movie, take for instance TIME = 400, DPHI = 0.00005, NVID = 100 */ /* Colors and other graphical parameters */ #define COLOR_PALETTE 11 /* Color palette, see list in global_pdes.c */ #define NCOLORS 64 /* number of colors */ #define COLORSHIFT 0 /* hue of initial color */ #define COLOR_HUEMIN 0 /* minimal color hue */ #define COLOR_HUEMAX 360 /* maximal color hue */ #define RAINBOW_COLOR 0 /* set to 1 to use different colors for all particles */ #define FLOWER_COLOR 0 /* set to 1 to adapt initial colors to flower billiard (tracks vs core) */ #define NSEG 100 /* number of segments of boundary */ #define LENGTH 0.03 /* length of velocity vectors */ #define BILLIARD_WIDTH 2 /* width of billiard */ #define PARTICLE_WIDTH 2 /* width of particles */ #define FRONT_WIDTH 3 /* width of wave front */ #define GRAPH_HUE 200.0 /* color hue of graph */ #define SUCCESS_HUE 30.0 /* color hue of success */ #define BLACK 1 /* set to 1 for black background */ #define COLOR_OUTSIDE 0 /* set to 1 for colored outside */ #define OUTER_COLOR 270.0 /* color outside billiard */ #define PAINT_INT 0 /* set to 1 to paint interior in other color (for polygon/Reuleaux) */ #define PAINT_EXT 1 /* set to 1 to paint exterior */ #define PAUSE 1000 /* number of frames after which to pause */ #define PSLEEP 2 /* sleep time during pause */ #define SLEEP1 1 /* initial sleeping time */ #define SLEEP2 1 /* final sleeping time */ #define END_FRAMES 250 /* number of frames at end of movie */ #define NXMAZE 25 /* width of maze */ #define NYMAZE 25 /* height of maze */ #define MAZE_MAX_NGBH 8 /* max number of neighbours of maze cell */ #define RAND_SHIFT 10 /* seed of random number generator */ #define MAZE_XSHIFT -0.7 /* horizontal shift of maze */ #define MAZE_RANDOM_FACTOR 0.1 /* randomization factor for S_MAZE_RANDOM */ #define MAZE_CORNER_RADIUS 0.4 /* radius of tounded corners in maze */ #define CLOSE_MAZE 0 /* set to 1 to close maze exits */ #include "global_particles.c" #include "sub_maze.c" #include "sub_part_billiard.c" /*********************/ /* animation part */ /*********************/ void init_boundary_config(double smin, double smax, double anglemin, double anglemax, double *configs[NPARTMAX]) /* initialize configuration: drop on the boundary, beta version */ /* WORKS FOR ELLIPSE, HAS TO BE ADAPTED TO GENERAL BILLIARD */ { int i; double ds, da, s, angle, theta, alpha, pos[2]; if (anglemin <= 0.0) anglemin = PI/((double)NPART); if (anglemax >= PI) anglemax = PI*(1.0 - 1.0/((double)NPART)); ds = (smax - smin)/((double)NPART); da = (anglemax - anglemin)/((double)NPART); for (i=0; i 1) dalpha = (angle2 - angle1)/((double)(NPART-1)); else dalpha = 0.0; for (i=0; i 1) dalpha = (angle2 - angle1)/((double)(particle2 - particle1-1)); else dalpha = 0.0; for (i=particle1; i=1; j--) { lum = 0.5*(1.0 - (double)j/(double)TRAJ_LENGTH); for (i=0; i 1.0) { yb = shifty + 0.5*(1.0 - y_target)/width; glBegin(GL_LINE_STRIP); glVertex2d(x1, yb); glVertex2d(x2, yb); glVertex2d(x2, yb + 0.02); glVertex2d(x1, yb + 0.02); glEnd(); } /* other boundaries not yet implemented */ /* draw target in zoom */ glLineWidth(BILLIARD_WIDTH*2); if (shooter) glColor3f(1.0, 0.0, 0.0); else glColor3f(0.0, 0.8, 0.2); glBegin(GL_LINE_LOOP); tradius = zoomwidth*MU/width; for (i=0; i<=NSEG; i++) { phi = (double)i*DPI/(double)NSEG; x1 = shiftx + tradius*cos(phi); y1 = shifty + tradius*sin(phi); glVertex2d(x1, y1); } glEnd (); // glLineWidth(PARTICLE_WIDTH*2); } void draw_zoom_area(double x_target, double y_target, double width) /* draw rectangle marking zoomed area */ { double x1, x2, y1, y2; glEnable(GL_LINE_SMOOTH); glLineWidth(BILLIARD_WIDTH/2); x1 = x_target - width; y1 = y_target - width; x2 = x_target + width; y2 = y_target + width; glColor3f(1.0, 1.0, 1.0); glBegin(GL_LINE_LOOP); glVertex2d(x1, y1); glVertex2d(x2, y1); glVertex2d(x2, y2); glVertex2d(x1, y2); glEnd(); } void draw_trajectories_lshape(int color[NPARTMAX], double *trajectory[NPARTMAX], int traj_length[NPARTMAX]) /* draw the trajectories */ { int i, j; double rgb[3], lum, x1, y1, x2, y2, x3, y3, x4, y4; for (j=TRAJ_LENGTH-2; j>=1; j--) { lum = 0.5*(1.0 - (double)j/(double)TRAJ_LENGTH); for (i=0; i= 0.0) x3 = 0.0; else x3 = 1.0; y3 = y1; } else if ((x1 == 0.0)||(x1 == 1.0)) { x3 = -1.0; y3 = y1; } if (y1 == -1.0) { if (x1 >= 0.0) y3 = 0.0; else y3 = 1.0; x3 = x1; } else if ((y1 == 0.0)||(y1 == 1.0)) { y3 = -1.0; x3 = x1; } if (j < traj_length[i]) { rgb_color_scheme_lum(color[i], lum, rgb); glColor3f(rgb[0], rgb[1], rgb[2]); // glBegin(GL_LINE_STRIP); // glVertex2d(x1, y1); // glVertex2d(x2, y2); // glEnd (); glBegin(GL_LINE_STRIP); glVertex2d(x2, y2); glVertex2d(x3, y3); glEnd (); } } } } void draw_trajectories(int color[NPARTMAX], double *trajectory[NPARTMAX], int traj_length[NPARTMAX]) /* draw the trajectories */ { int i, j, col; double rgb[3], lum, x1, y1, x2, y2; glutSwapBuffers(); if (!SHOWTRAILS) blank(); if (PAINT_INT) paint_billiard_interior(); if (SHOWZOOM) switch (POLYLINE_PATTERN) { case (P_TOKA_PRIME): { draw_zoom(color, trajectory, traj_length, x_target, y_target, 0.05, 1.65, 0.75, 0.3, 0); draw_zoom(color, trajectory, traj_length, x_shooter, y_shooter, 0.05, -1.65, 0.75, 0.3, 1); break; } case (P_TOKA_NONSELF): { draw_zoom(color, trajectory, traj_length, 0.0, 0.0, 0.1, 1.65, 0.75, 0.3, 1); break; } case (P_ISOCELES_TRIANGLE): { draw_zoom(color, trajectory, traj_length, 1.0, -1.0, 0.2, 0.6, 0.6, 0.4, 0); break; } } glLineWidth(PARTICLE_WIDTH); glEnable(GL_LINE_SMOOTH); /* if domain is L shape with periodic boundary conditions, we have to take care of those */ if (B_DOMAIN == D_LSHAPE) draw_trajectories_lshape(color, trajectory, traj_length); else for (j=TRAJ_LENGTH-1; j>=1; j--) { lum = 0.5*(1.0 - 0.5*(double)j/(double)TRAJ_LENGTH); for (i=0; i=1; j--) if (j < traj_length[i]) { x1 = trajectory[i][2*j]; y1 = trajectory[i][2*j+1]; x2 = trajectory[i][2*j-2]; y2 = trajectory[i][2*j-1]; len = module2(x2 - x1, y2 - y1); for (k=0; k<(int)(len/pathstep); k++) { x = x1 + (x2 - x1)*(double)k*pathstep/len; y = y1 + (y2 - y1)*(double)k*pathstep/len; xtable[i] = x; ytable[i] = y; /* test whether particle does not escape billiard */ if ((TEST_ACTIVE)&&(active[i])) active[i] = xy_in_billiard(x, y); if (active[i]) { n = find_maze_cell(x, y); if ((n > -1)&&(n < NXMAZE*NYMAZE+1)) { heatmap_number[n]++; heatmap_total[n]++; } // if (HEATMAP_MAX_PART_BY_CELL > 0) // { // drawtable[i] = ((n == -2)||((n > 0)&&(heatmap_number[n] <= HEATMAP_MAX_PART_BY_CELL))); // } // else drawtable[i] = (n > -1); } } } } for (n=0; n= 100) shiftx = 0.1; else if (j >= 10) shiftx = 0.0775; else shiftx = 0.055; write_text_fixedwidth(xmin - shiftx, y - 0.01, message); } /* horizontal axis graduation */ for (j=0; j iw) x -= c*(double)(i - iw); if ((x > xmin)&&(x < xmax - 0.05)) { y = ymin - 0.025; y1 = ymin + 0.025; draw_line(x, y, x, y1); if (x < xmax - 0.15) { sprintf(message, "%i", jmin + j); if (j < 100) x += 0.01; if (j < 10) x += 0.015; write_text_fixedwidth(x - 0.04, ymin - 0.075, message); } } } /* to reduce aliasing, plot a broader graph at smaller luminosity */ glLineWidth(3); if (TEST_INITIAL_COND) { glColor3f(rgb1_success[0], rgb1_success[1], rgb1_success[2]); for (j=imin; jimin) { draw_line(x0, y0, x, y0); draw_line(x, y0, x, y); } x0 = x; y0 = y; } glLineWidth(2); x0 = xmin; y0 = ymin; if (TEST_INITIAL_COND) { glColor3f(rgb_success[0], rgb_success[1], rgb_success[2]); for (j=imin; jimin) { draw_line(x0, y0, x, y0); draw_line(x, y0, x, y); } x0 = x; y0 = y; } draw_initial_condition_circle(x0, y0, 0.02, 30); // draw_initial_condition_circle(x0, y0, 0.02, traj_length_table[i]); } void plot_lengths_polar(int traj_length_table[NSTEPS+1], short int traj_test_table[NSTEPS+1], int i) /* draw a plot of thajectory lengths */ { int j, k; double xc, yc, x0, y0, x1, y1, xmin = 0.5, xmax = 1.9, ymin = -0.7, ymax = 0.3, phi, dphi, r; char message[50]; static int first = 1; static double rgb[3], rgb_success[3], logfactor; if (first) { rgb_color_scheme(GRAPH_HUE, rgb); rgb_color_scheme(SUCCESS_HUE, rgb_success); logfactor = 0.07; } dphi = DPI/(double)NSTEPS; xc = 0.5*(xmin + xmax); yc = 0.5*(ymin + ymax); glColor3f(1.0, 1.0, 1.0); glLineWidth(1); /* draw grid */ r = 0.5*(xmax - xmin); for (j=0; j<12; j++) { phi = (double)j*DPI/12.0; draw_line(xc, yc, xc + r*cos(phi), yc + r*sin(phi)); } for (j=1; j<5; j++) { r = logfactor*(double)j*log(10.0); draw_circle(xc, yc, r, NSEG); } glLineWidth(2); /* draw plot of succesful paths */ // if (TEST_INITIAL_COND) // { // r = 0.5*(xmax - xmin); // glColor3f(rgb_success[0], rgb_success[1], rgb_success[2]); // for (j=0; j 1) dalpha = (angle2 - angle1)/((double)(NPART)); else dalpha = 0.0; for (i=0; i= 0.0)&&(traj_length[i] < TRAJ_LENGTH - 1)) { // c = vbilliard(configs[i]); pos_billiard(configs[i], pos, &alpha); c = vbilliard_xy(configs[i], alpha, pos); j = traj_length[i]; trajectory[i][2*j] = configs[i][4]; trajectory[i][2*j+1] = configs[i][5]; traj_length[i]++; if ((!loop)&&(i == 0)) { if (module2(configs[i][0] - s0, configs[i][1] - u0) > 0.001) period++; else loop = 1; } } if (i==0) c0 = c; j = traj_length[i]; trajectory[i][2*j] = configs[i][6]; trajectory[i][2*j+1] = configs[i][7]; traj_length[i]++; } *xmax = trajectory[0][2*(traj_length[0]-1)]; // printf("Period = %i\n", period); if ((c0 == DUMMY_SIDE_ABS)||(c0 < 0)) return(0); else return(period + 1); // if ((c != DUMMY_SIDE_ABS)&&(c >= 0.0)) return (period); // else return(0); } void compute_trajectories_ellipse(double x, double y, double *configs[NPARTMAX], double *trajectory[NPARTMAX], int traj_length[NPARTMAX]) /* compute trajectories when starting in (x,y), with angles between angle1 and angle2 */ { int i, j, c; double dalpha, alpha, a, b, cc, dx, d; double conf[2], pos[2]; cc = sqrt(LAMBDA*LAMBDA - 1.0); dx = 4.0*cc/(double)NPART; for (i=0; i XMAX)||((EXIT_BOTH_WAYS)&&(xmax < XMIN))) { traj_test_table[i] = 1; nsuccess++; } else traj_test_table[i] = 0; // traj_test_table[i] = test_initial_condition(configs, active, newcolor); // if (traj_test_table[i] >= 1) nsuccess++; } // printf("Period = %i\n", period); draw_trajectories(color, trajectory, traj_length); if (HEATMAP) draw_config_heatmap(trajectory, traj_length, configs, active, heatmap_number, heatmap_total, 0); // else draw_trajectories(color, trajectory, traj_length); print_parameters(i, traj_length, traj_length_table, period, alpha); if (DRAW_BILLIARD) draw_billiard(); if (DRAW_LENGTHS_PLOT) plot_lengths(traj_length_table, traj_test_table, i+1); /* draw the shooter in red */ rgb[0] = 1.0; rgb[1] = 0.0; rgb[2] = 0.0; draw_colored_circle(x, y, 0.01, NSEG, rgb); // draw_colored_circle(x_shooter, y_shooter, 0.01, NSEG, rgb); for (j=0; j= 10)) { npause = (int)(0.5*sqrt((double)traj_length[0])); for (j=0; j0)&&(traj_test_table[i])) { // sleep(0.5); /* additional buffer swap seems to be necessary on some OS */ glutSwapBuffers(); if (first == 0) { // gallery_counter = NSTEPS + END_FRAMES + success_counter*SUCCESS_GALLERY_FRAMES; glutSwapBuffers(); first = 1; } else { for (j=0; j= nend) nend = gallery_counter - NSTEPS; for (i=0; i