461 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * ansi_graphics.h
 | |
|  * A couple of function to display graphics in the terminal, 
 | |
|  * using ansi sequences.
 | |
|  * Bruno Levy, Jan 2024
 | |
|  */
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <math.h>
 | |
| #include <stdint.h>
 | |
| #include <stdlib.h>
 | |
| 
 | |
| #ifndef GL_FPS
 | |
| #define GL_FPS 30
 | |
| #endif
 | |
| 
 | |
| #if defined(__linux__) || defined(_WIN32) || defined(__APPLE__)
 | |
| #define BIGCPU  // we are compiling for a real machine
 | |
| #else
 | |
| #define TINYCPU // we are compiling for a softwore
 | |
| #endif
 | |
| 
 | |
| #ifdef __linux__
 | |
| #include <unistd.h> // for usleep()
 | |
| #endif
 | |
| 
 | |
| // You can define GL_width and GL_height before
 | |
| // #including ansi_graphics.h in case the plain
 | |
| // old 80x25 pixels does not suffice.
 | |
| 
 | |
| #ifndef GL_width
 | |
| #define GL_width  80
 | |
| #endif
 | |
| 
 | |
| #ifndef GL_height
 | |
| #define GL_height 25
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * \brief Sets the current graphics position
 | |
|  * \param[in] x typically in 0,79
 | |
|  * \param[in] y typically in 0,24
 | |
|  */
 | |
| static inline void GL_gotoxy(int x, int y) {
 | |
|     printf("\033[%d;%dH",y,x);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Sets the current graphics position
 | |
|  * \param[in] R , G , B the RGB color of the pixel, in [0..255]
 | |
|  * \details Typically used by programs that draw all pixels sequentially,
 | |
|  *  like a raytracer. After each line, one can either printf("\n") or
 | |
|  *  call GL_gotoxy(). If you want to draw individual pixels in an
 | |
|  *  arbitrary order, use GL_setpixelRGB(x,y,R,G,B)
 | |
|  */
 | |
| static inline void GL_setpixelRGBhere(uint8_t R, uint8_t G, uint8_t B) {
 | |
|     // set background color, print space 
 | |
|     printf("\033[48;2;%d;%d;%dm ",(int)R,(int)G,(int)B); 
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * \brief Draws two "pixels" at the current
 | |
|  *  cursor position and advances the current cursor
 | |
|  *  position.
 | |
|  * \details Characters are roughly twice as high as wide.
 | |
|  *  To generate square pixels, this function draws two pixels in
 | |
|  *  the same character, using the special lower-half white / upper-half
 | |
|  *  black character, and setting the background and foreground colors.
 | |
|  */
 | |
| static inline void GL_set2pixelsRGBhere(
 | |
|     uint8_t r1, uint8_t g1, uint8_t b1,
 | |
|     uint8_t r2, uint8_t g2, uint8_t b2
 | |
| ) {
 | |
|     if((r2 == r1) && (g2 == g1) && (b2 == b1)) {
 | |
| 	GL_setpixelRGBhere(r1,g1,b1);
 | |
|     } else {
 | |
| 	printf("\033[48;2;%d;%d;%dm",(int)r1,(int)g1,(int)b1);	   	   
 | |
| 	printf("\033[38;2;%d;%d;%dm",(int)r2,(int)g2,(int)b2);
 | |
| 	// https://www.w3.org/TR/xml-entity-names/025.html
 | |
| 	// https://onlineunicodetools.com/convert-unicode-to-utf8
 | |
| 	// https://copypastecharacter.com/
 | |
| 	printf("\xE2\x96\x83");
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define GL_RGB(R,G,B) #R ";" #G ";" #B 
 | |
| 
 | |
| static inline void GL_setpixelIhere(
 | |
|     const char** cmap, int c
 | |
| ) {
 | |
|     // set background color, print space 
 | |
|     printf("\033[48;2;%sm ",cmap[c]); 
 | |
| }
 | |
| 
 | |
| static inline void GL_set2pixelsIhere(
 | |
|     const char** cmap, int c1, int c2
 | |
| ) {
 | |
|     if(c1 == c2) {
 | |
| 	GL_setpixelIhere(cmap, c1);
 | |
|     } else {
 | |
| 	printf("\033[48;2;%sm",cmap[c1]);	   	   
 | |
| 	printf("\033[38;2;%sm",cmap[c2]);
 | |
| 	// https://www.w3.org/TR/xml-entity-names/025.html
 | |
| 	// https://onlineunicodetools.com/convert-unicode-to-utf8
 | |
| 	// https://copypastecharacter.com/
 | |
| 	printf("\xE2\x96\x83");
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Moves the cursor position to the next line.
 | |
|  * \details Background and foreground colors are set to black.
 | |
|  */
 | |
| static inline void GL_newline() {
 | |
|     printf("\033[38;2;0;0;0m");	   
 | |
|     printf("\033[48;2;0;0;0m\n");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Sets the color of a pixel
 | |
|  * \param[in] x typically in 0,79
 | |
|  * \param[in] y typically in 0,24
 | |
|  * \param[in] R , G , B the RGB color of the pixel, in [0..255]
 | |
|  */
 | |
| static inline void GL_setpixelRGB(
 | |
|     int x, int y, uint8_t R, uint8_t G, uint8_t B
 | |
| ) {
 | |
|     GL_gotoxy(x,y);
 | |
|     GL_setpixelRGBhere(R,G,B);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief restore default foreground and background colors
 | |
|  */
 | |
| static inline void GL_restore_default_colors() {
 | |
|     printf(
 | |
|         "\033[48;5;16m"   // set background color black
 | |
|         "\033[38;5;15m"   // set foreground color white
 | |
|     );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Call this function each time graphics should be cleared
 | |
|  */
 | |
| static inline void GL_clear() {
 | |
|     GL_restore_default_colors();
 | |
|     printf("\033[2J"); // clear screen
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Moves current drawing position to top-left corner
 | |
|  * \see GL_setpixelRGBhere() and GL_set2pixelsRGBhere()
 | |
|  */
 | |
| static inline void GL_home() {
 | |
|     printf("\033[H");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Call this function before starting drawing graphics 
 | |
|  *  or each time graphics should be cleared
 | |
|  */
 | |
| static inline void GL_init() {
 | |
|     printf("\033[?25l"); // hide cursor
 | |
|     GL_home();
 | |
|     GL_clear();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * \brief Call this function at the end of the program
 | |
|  */
 | |
| static inline void GL_terminate() {
 | |
|     GL_restore_default_colors();
 | |
|     GL_gotoxy(0,GL_height);
 | |
|     printf("\033[?25h"); // show cursor
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Flushes pending graphic operations and waits a bit
 | |
|  */
 | |
| static inline void GL_swapbuffers() {
 | |
|     // only flush if we are on a big machine, with true stdio support
 | |
|     // otherwise does nothing (because our small MCU io lib is not buffered)
 | |
| #ifdef BIGCPU    
 | |
|    fflush(stdout);
 | |
| #endif
 | |
| #ifdef __linux__   
 | |
|    usleep(1000000/GL_FPS);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| typedef void (*GL_pixelfunc_RGB)(int x, int y, uint8_t* r, uint8_t* g, uint8_t* b);
 | |
| typedef void (*GL_pixelfunc_RGBf)(int x, int y, float* r, float* g, float* b);
 | |
| 
 | |
| /**
 | |
|  * \brief Draws an image by calling a user-specified function for each pixel.
 | |
|  * \param[in] width , height dimension of the image in square pixels
 | |
|  * \param[in] do_pixel the user function to be called for each pixel 
 | |
|  *  (a "shader"), that determines the (integer) components r,g,b of 
 | |
|  *   the pixel's color.
 | |
|  * \details Uses half-charater pixels.
 | |
|  */
 | |
| static inline void GL_scan_RGB(
 | |
|     int width, int height, GL_pixelfunc_RGB do_pixel
 | |
| ) {
 | |
|     uint8_t r1, g1, b1;
 | |
|     uint8_t r2, g2, b2;
 | |
|     GL_home(); 
 | |
|     for (int j = 0; j<height; j+=2) { 
 | |
| 	for (int i = 0; i<width; i++) {
 | |
| 	    do_pixel(i,j  , &r1, &g1, &b1);
 | |
| 	    do_pixel(i,j+1, &r2, &g2, &b2);
 | |
| 	    GL_set2pixelsRGBhere(r1,g1,b1,r2,g2,b2);
 | |
| 	    if(i == width-1) {
 | |
| 		GL_newline();
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * brief Converts a floating point value to a byte.
 | |
|  * \param[in] the floating point value in [0,1]
 | |
|  * \return the byte, in [0,255]
 | |
|  * \details the input value is clamped to [0,1]
 | |
|  */ 
 | |
| static inline uint8_t GL_ftoi(float f) {
 | |
|     f = (f < 0.0f) ? 0.0f : f;
 | |
|     f = (f > 1.0f) ? 1.0f : f;
 | |
|     return (uint8_t)(255.0f * f);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Draws an image by calling a user-specified function for each pixel.
 | |
|  * \param[in] width , height dimension of the image in square pixels
 | |
|  * \param[in] do_pixel the user function to be called for each pixel 
 | |
|  *  (a "shader"), that determines the (floating-point) components 
 | |
|  *  fr,fg,fb of the pixel's color.
 | |
|  * \details Uses half-charater pixels.
 | |
|  */
 | |
| static inline void GL_scan_RGBf(
 | |
|     int width, int height, GL_pixelfunc_RGBf do_pixel
 | |
| ) {
 | |
|     float fr1, fg1, fb1;
 | |
|     float fr2, fg2, fb2;
 | |
|     uint8_t r1, g1, b1;
 | |
|     uint8_t r2, g2, b2;
 | |
|     GL_home();
 | |
|     for (int j = 0; j<height; j+=2) { 
 | |
| 	for (int i = 0; i<width; i++) {
 | |
| 	    do_pixel(i,j  , &fr1, &fg1, &fb1);
 | |
| 	    r1 = GL_ftoi(fr1);
 | |
| 	    g1 = GL_ftoi(fg1);
 | |
| 	    b1 = GL_ftoi(fb1);	    
 | |
| 	    do_pixel(i,j+1, &fr2, &fg2, &fb2);
 | |
| 	    r2 = GL_ftoi(fr2);
 | |
| 	    g2 = GL_ftoi(fg2);
 | |
| 	    b2 = GL_ftoi(fb2);	    
 | |
| 	    GL_set2pixelsRGBhere(r1,g1,b1,r2,g2,b2);
 | |
| 	    if(i == width-1) {
 | |
| 		GL_newline();
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| /***************************************************************/
 | |
| 
 | |
| #define INSIDE 0
 | |
| #define LEFT   1
 | |
| #define RIGHT  2
 | |
| #define BOTTOM 4
 | |
| #define TOP    8
 | |
| 
 | |
| #define XMIN 0
 | |
| #define XMAX (GL_width-1)
 | |
| #define YMIN 0
 | |
| #define YMAX (GL_height-1)
 | |
| 
 | |
| #define code(x,y) \
 | |
|     ((x) < XMIN) | (((x) > XMAX)<<1) | (((y) < YMIN)<<2) | (((y) > YMAX)<<3) 
 | |
| 
 | |
| /***************************************************************/
 | |
| 
 | |
| static inline void GL_line(
 | |
|     int x1, int y1, int x2, int y2, int R, int G, int B
 | |
| ) {
 | |
|     int x,y,dx,dy,sx,sy,tmp;
 | |
| 
 | |
|     /* Cohen-Sutherland line clipping. */
 | |
|     int code1 = code(x1,y1);
 | |
|     int code2 = code(x2,y2);
 | |
|     int codeout;
 | |
| 
 | |
|     for(;;) {
 | |
| 	/* Both points inside. */
 | |
| 	if(code1 == 0 && code2 == 0) {
 | |
| 	    break;
 | |
| 	}
 | |
| 
 | |
| 	/* No point inside. */
 | |
| 	if(code1 & code2) {
 | |
| 	    return;
 | |
| 	}
 | |
| 
 | |
| 	/* One of the points is outside. */
 | |
| 	codeout = code1 ? code1 : code2;
 | |
| 
 | |
| 	/* Compute intersection. */
 | |
| 	if (codeout & TOP) { 
 | |
| 	    x = x1 + (x2 - x1) * (YMAX - y1) / (y2 - y1); 
 | |
| 	    y = YMAX; 
 | |
| 	} else if (codeout & BOTTOM) { 
 | |
| 	    x = x1 + (x2 - x1) * (YMIN - y1) / (y2 - y1); 
 | |
| 	    y = YMIN; 
 | |
| 	}  else if (codeout & RIGHT) { 
 | |
| 	    y = y1 + (y2 - y1) * (XMAX - x1) / (x2 - x1); 
 | |
| 	    x = XMAX; 
 | |
| 	} else if (codeout & LEFT) { 
 | |
| 	    y = y1 + (y2 - y1) * (XMIN - x1) / (x2 - x1); 
 | |
| 	    x = XMIN; 
 | |
| 	} 
 | |
| 	
 | |
| 	/* Replace outside point with intersection. */
 | |
| 	if (codeout == code1) { 
 | |
| 	    x1 = x; 
 | |
| 	    y1 = y;
 | |
| 	    code1 = code(x1,y1);
 | |
| 	} else { 
 | |
| 	    x2 = x; 
 | |
| 	    y2 = y;
 | |
| 	    code2 = code(x2,y2);
 | |
| 	}
 | |
|     }
 | |
|     
 | |
|     // Swap both extremities to ensure x increases
 | |
|     if(x2 < x1) {
 | |
|        tmp = x2;
 | |
|        x2 = x1;
 | |
|        x1 = tmp;
 | |
|        tmp = y2;
 | |
|        y2 = y1;
 | |
|        y1 = tmp;
 | |
|     }
 | |
|    
 | |
|     // Bresenham line drawing.
 | |
|     dy = y2 - y1;
 | |
|     sy = 1;
 | |
|     if(dy < 0) {
 | |
| 	sy = -1;
 | |
| 	dy = -dy;
 | |
|     }
 | |
| 
 | |
|     dx = x2 - x1;
 | |
|    
 | |
|     x = x1;
 | |
|     y = y1;
 | |
|     
 | |
|     if(dy > dx) {
 | |
| 	int ex = (dx << 1) - dy;
 | |
| 	for(int u=0; u<dy; u++) {
 | |
| 	    GL_setpixelRGB(x,y,R,G,B);
 | |
| 	    y += sy;
 | |
| 	    if(ex >= 0)  {
 | |
| 		x++;
 | |
| 		ex -= dy << 1;
 | |
| 		GL_setpixelRGB(x,y,R,G,B);
 | |
| 	    }
 | |
| 	    while(ex >= 0)  {
 | |
| 		x++;
 | |
| 		ex -= dy << 1;
 | |
| 	        putchar(' ');
 | |
| 	    }
 | |
| 	    ex += dx << 1;
 | |
| 	}
 | |
|     } else {
 | |
| 	int ey = (dy << 1) - dx;
 | |
| 	for(int u=0; u<dx; u++) {
 | |
| 	    GL_setpixelRGB(x,y,R,G,B);
 | |
| 	    x++;
 | |
| 	    while(ey >= 0) {
 | |
| 		y += sy;
 | |
| 		ey -= dx << 1;
 | |
| 		GL_setpixelRGB(x,y,R,G,B);
 | |
| 	    }
 | |
| 	    ey += dy << 1;
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /***************************************************************/
 | |
| 
 | |
| #ifdef GL_USE_TURTLE
 | |
| 
 | |
| #include "sintab.h" // Ugly !!!
 | |
| 
 | |
| typedef struct {
 | |
|     int x;        // in [0..79]
 | |
|     int y;        // in [0..24]
 | |
|     int angle;    // in degrees
 | |
|     int R,G,B;    // pen color
 | |
|     int pendown;  // draw if non-zero
 | |
| } Turtle;
 | |
|     
 | |
| static inline void Turtle_init(Turtle* T) {
 | |
|     T->x = GL_width/2;
 | |
|     T->y = GL_height/2;
 | |
|     T->angle = -90;
 | |
|     T->pendown = 1;
 | |
|     T->R = 255;
 | |
|     T->G = 255;
 | |
|     T->B = 255;
 | |
| }
 | |
| 
 | |
| static inline void Turtle_pen_up(Turtle* T) {
 | |
|     T->pendown = 0;
 | |
| }
 | |
| 
 | |
| static inline void Turtle_pen_down(Turtle* T) {
 | |
|     T->pendown = 1;
 | |
| }
 | |
| 
 | |
| static inline void Turtle_pen_color(Turtle* T, int R, int G, int B) {
 | |
|     T->R = R;
 | |
|     T->G = G;
 | |
|     T->B = B;
 | |
| }
 | |
| 
 | |
| static inline void Turtle_forward(Turtle* T, int distance) {
 | |
|     int last_x = T->x;
 | |
|     int last_y = T->y;
 | |
|     int a = T->angle;
 | |
|     while(a < 0) {
 | |
|         a += 360;
 | |
|     }
 | |
|     while(a > 360) {
 | |
|         a -= 360;
 | |
|     }
 | |
|     T->x += (costab[a] * distance) / 256;
 | |
|     T->y += (sintab[a] * distance) / 256;
 | |
|     if(T->pendown) {
 | |
|         GL_line(last_x, last_y, T->x, T->y, T->R, T->G, T->B);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static inline void Turtle_backward(Turtle* T, int distance) {
 | |
|     Turtle_forward(T,-distance);
 | |
| }
 | |
| 
 | |
| static inline void Turtle_turn_right(Turtle* T, int delta_angle) {
 | |
|     T->angle += delta_angle;
 | |
| }
 | |
| 
 | |
| static inline void Turtle_turn_left(Turtle* T, int delta_angle) {
 | |
|     Turtle_turn_right(T, -delta_angle);
 | |
| }
 | |
| 
 | |
| #endif
 |