2323 * Modified by Jim Huang <[email protected] > 2424 * - Refine the comments
2525 * - Colorize the renderer
26- * - Support nanosleep
26+ * - Adapt ANSI graphical enhancements from Bruno Levy
2727 */
2828
2929/* An ASCII donut renderer that relies solely on shifts, additions,
3535#include <stdint.h>
3636#include <stdio.h>
3737#include <string.h>
38- #include <time.h>
38+ #if !defined(__riscv )
39+ #include <unistd.h>
40+ #endif
3941
40- /* 0 for 80x24, 1 for 160x48, etc. */
41- enum {
42- RESX_SHIFT = 0 ,
43- RESY_SHIFT = 0 ,
42+ /* Define 1 for a more accurate result (but it costs a bit) */
43+ #define PRECISE 0
44+
45+ static const char * colormap [34 ] = {
46+ "0" , "8;5;232" , "8;5;233" , "8;5;234" , "8;5;235" , "8;5;236" , "8;5;237" ,
47+ "8;5;238" , "8;5;239" , "8;5;240" , "8;5;241" , "8;5;242" , "8;5;243" , "8;5;244" ,
48+ "8;5;245" , "8;5;246" , "8;5;247" , "8;5;248" , "8;5;249" , "8;5;250" , "8;5;251" ,
49+ "8;5;252" , "8;5;253" , "8;5;254" , "8;5;255" , "7" , "8;5;16" , "8;5;17" ,
50+ "8;5;18" , "8;5;19" , "8;5;20" , "8;5;21" , "8;5;22" , "8;5;23" ,
4451};
4552
53+ /* Previous background/foreground colors */
54+ static int prev_color1 = 0 , prev_color2 = 0 ;
55+
56+ static inline void setcolors (int fg /* foreground */ , int bg /* background */ )
57+ {
58+ printf ("\033[4%s;3%sm" , colormap [bg ], colormap [fg ]);
59+ }
60+
61+ static inline void setpixel (int x , int y , int color )
62+ {
63+ /* Stash the "upper" scanline so we can combine two rows of output. */
64+ static char scanline [80 ];
65+ int c1 , c2 ;
66+
67+ /* On even row (y & 1 == 0), just remember the color; no output yet. */
68+ if (!(y & 1 )) {
69+ scanline [x ] = color ;
70+ return ;
71+ }
72+
73+ /* On the odd row, pull the stored color from the previous row. */
74+ c1 = scanline [x ]; /* background */
75+ c2 = color ; /* foreground */
76+
77+ /* Same background/foreground: print a space with only background color */
78+ if (c1 == c2 ) {
79+ if (prev_color1 != c1 ) {
80+ printf ("\033[4%sm " , colormap [c1 ]);
81+ prev_color1 = c1 ;
82+ } else { /* Already set, just print a space */
83+ putchar (' ' );
84+ }
85+ return ;
86+ }
87+
88+ /* Different colors: print a block with new bg/fg if either changed */
89+ if (prev_color1 != c1 || prev_color2 != c2 ) {
90+ printf ("\033[4%s;3%sm" , colormap [c1 ], colormap [c2 ]);
91+ prev_color1 = c1 ;
92+ prev_color2 = c2 ;
93+ }
94+ printf ("\u2583" );
95+ }
96+
4697/* Torus radius and camera distance.
4798 * These values are closely tied to other constants, so modifying them
4899 * significantly may lead to unexpected behavior.
@@ -60,6 +111,12 @@ static const int dz = 5, r1 = 1, r2 = 2;
60111 x -= (y >> s); \
61112 y += (x >> s)
62113
114+ #if PRECISE
115+ #define N_CORDIC 10
116+ #else
117+ #define N_CORDIC 6
118+ #endif
119+
63120/* CORDIC algorithm used to calculate the magnitude of the vector |x, y| by
64121 * rotating the vector onto the x-axis. This operation also transforms vector
65122 * (x2, y2) accordingly, and updates the value of x2. This rotation is employed
@@ -68,87 +125,98 @@ static const int dz = 5, r1 = 1, r2 = 2;
68125 * noting that only one of the two lighting normal coordinates needs to be
69126 * retained.
70127 */
71- #define N_CORDIC 6
72128static int length_cordic (int16_t x , int16_t y , int16_t * x2_ , int16_t y2 )
73129{
74130 int x2 = * x2_ ;
75- if (x < 0 ) { // start in right half-plane
131+
132+ /* Move x into the right half-plane */
133+ if (x < 0 ) {
76134 x = - x ;
77135 x2 = - x2 ;
78136 }
137+
138+ /* CORDIC iterations */
79139 for (int i = 0 ; i < N_CORDIC ; i ++ ) {
80- int t = x ;
81- int t2 = x2 ;
82- if (y < 0 ) {
83- x -= y >> i ;
84- y += t >> i ;
85- x2 -= y2 >> i ;
86- y2 += t2 >> i ;
87- } else {
88- x += y >> i ;
89- y -= t >> i ;
90- x2 += y2 >> i ;
91- y2 -= t2 >> i ;
92- }
140+ int t = x , t2 = x2 ;
141+ int sign = (y < 0 ) ? -1 : 1 ;
142+
143+ x += sign * (y >> i );
144+ y -= sign * (t >> i );
145+ x2 += sign * (y2 >> i );
146+ y2 -= sign * (t2 >> i );
93147 }
94- /* Divide by 0.625 as a rough approximation to the 0.607 scaling factor
95- * introduced by this algorithm
96- * See https://en.wikipedia.org/wiki/CORDIC
148+
149+ /* Divide by ~0.625 (5/8) to approximate the 0.607 scaling factor
150+ * introduced by the CORDIC algorithm. See:
151+ * https://en.wikipedia.org/wiki/CORDIC
97152 */
98- * x2_ = (x2 >> 1 ) + (x2 >> 3 ) - (x2 >> 6 );
153+ * x2_ = (x2 >> 1 ) + (x2 >> 3 );
154+ #if PRECISE
155+ * x2_ -= x2 >> 6 ; /* get closer to 0.607 */
156+ #endif
157+
99158 return (x >> 1 ) + (x >> 3 ) - (x >> 6 );
100159}
101160
102161int main ()
103162{
163+ printf (
164+ "\033[48;5;16m" /* set background color black */
165+ "\033[38;5;15m" /* set foreground color white */
166+ "\033[H" /* move cursor home */
167+ "\033[?25l" /* hide cursor */
168+ "\033[2J" ); /* clear screen */
169+
170+ int frame = 1 ;
171+
104172 /* Precise rotation directions, sines, cosines, and their products */
105173 int16_t sB = 0 , cB = 16384 ;
106174 int16_t sA = 11583 , cA = 11583 ;
107175 int16_t sAsB = 0 , cAsB = 0 ;
108176 int16_t sAcB = 11583 , cAcB = 11583 ;
109177
110178 for (int count = 0 ; count < 500 ; count ++ ) {
111- /* This is a multiplication, but since dz is 5, it is equivalent to
179+ /* Starting position (p0).
180+ * This is a multiplication, but since dz is 5, it is equivalent to
112181 * (sb + (sb << 2)) >> 6.
113182 */
114183 const int16_t p0x = dz * sB >> 6 ;
115184 const int16_t p0y = dz * sAcB >> 6 ;
116185 const int16_t p0z = - dz * cAcB >> 6 ;
117186
118- const int r1i = r1 * 256 ;
119- const int r2i = r2 * 256 ;
187+ const int r1i = r1 * 256 , r2i = r2 * 256 ;
188+
189+ int n_iters = 0 ;
120190
121- int niters = 0 ;
122- int nnormals = 0 ;
123191 /* per-row increments
124192 * These can all be compiled into two shifts and an add.
125193 */
126- int16_t yincC = (12 * cA ) >> ( 8 + RESY_SHIFT );
127- int16_t yincS = (12 * sA ) >> ( 8 + RESY_SHIFT );
194+ int16_t yincC = (cA >> 6 ) + ( cA >> 5 ); /* 12*cA >> 8 */
195+ int16_t yincS = (sA >> 6 ) + ( sA >> 5 ); /* 12*sA >> 8 */
128196
129197 /* per-column increments */
130- int16_t xincX = (6 * cB ) >> ( 8 + RESX_SHIFT );
131- int16_t xincY = (6 * sAsB ) >> ( 8 + RESX_SHIFT );
132- int16_t xincZ = (6 * cAsB ) >> ( 8 + RESX_SHIFT );
198+ int16_t xincX = (cB >> 7 ) + ( cB >> 6 ); /* 6*cB >> 8 */
199+ int16_t xincY = (sAsB >> 7 ) + ( sAsB >> 6 ); /* 6* sAsB >> 8 */
200+ int16_t xincZ = (cAsB >> 7 ) + ( cAsB >> 6 ); /* 6* cAsB >> 8 */
133201
134202 /* top row y cosine/sine */
135- int16_t ycA = - ((cA >> 1 ) + (cA >> 4 )); // -12 * yinc1 = -9*cA >> 4;
136- int16_t ysA = - ((sA >> 1 ) + (sA >> 4 )); // -12 * yinc2 = -9*sA >> 4;
203+ int16_t ycA = - ((cA >> 1 ) + (cA >> 4 )); /* -12 * yinc1 = -9*cA >> 4 */
204+ int16_t ysA = - ((sA >> 1 ) + (sA >> 4 )); /* -12 * yinc2 = -9*sA >> 4 */
137205
138- for (int j = 0 ; j < (24 << RESY_SHIFT ) - 1 ;
139- j ++ , ycA += yincC , ysA += yincS ) {
140- /* left columnn x cosines/sines */
141- int xsAsB = (sAsB >> 4 ) - sAsB ; // -40 * xincY
142- int xcAsB = (cAsB >> 4 ) - cAsB ; // -40 * xincZ;
206+ int xsAsB = (sAsB >> 4 ) - sAsB ; /* -40*xincY */
207+ int xcAsB = (cAsB >> 4 ) - cAsB ; /* -40*xincZ */
143208
209+ /* Render rows */
210+ for (int j = 0 ; j < 46 ; j ++ , ycA += yincC >> 1 , ysA += yincS >> 1 ) {
144211 /* ray direction */
145- int16_t vxi14 = (cB >> 4 ) - cB - sB ; // -40 * xincX - sB;
146- int16_t vyi14 = ( ycA - xsAsB - sAcB ) ;
147- int16_t vzi14 = ( ysA + xcAsB + cAcB ) ;
212+ int16_t vxi14 = (cB >> 4 ) - cB - sB ; // -40* xincX - sB;
213+ int16_t vyi14 = ycA - xsAsB - sAcB ;
214+ int16_t vzi14 = ysA + xcAsB + cAcB ;
148215
149- for (int i = 0 ; i < ((80 << RESX_SHIFT ) - 1 );
216+ /* Render columns */
217+ for (int i = 0 ; i < 79 ;
150218 i ++ , vxi14 += xincX , vyi14 -= xincY , vzi14 += xincZ ) {
151- int t = 512 ; // (256 * dz) - r2i - r1i;
219+ int t = 512 ; /* Depth accumulation: (256 * dz) - r2i - r1i */
152220
153221 /* Assume t = 512, t * vxi >> 8 == vxi << 1 */
154222 int16_t px = p0x + (vxi14 >> 5 );
@@ -158,6 +226,7 @@ int main()
158226 int16_t ly0 = (sAcB - cA ) >> 2 ;
159227 int16_t lz0 = (- cAcB - sA ) >> 2 ;
160228 for (;;) {
229+ /* Distance from torus surface */
161230 int t0 , t1 , t2 , d ;
162231 int16_t lx = lx0 , ly = ly0 , lz = lz0 ;
163232 t0 = length_cordic (px , py , & lx , ly );
@@ -166,15 +235,15 @@ int main()
166235 d = t2 - r1i ;
167236 t += d ;
168237
169- if (t > 8 * 256 ) {
170- putchar (' ' );
238+ if (t > (8 * 256 )) {
239+ int N = (((j - frame ) >> 3 ) ^ (((i + frame ) >> 3 ))) & 1 ;
240+ setpixel (i , j , (N << 2 ) + 26 );
171241 break ;
172- } else if (d < 2 ) {
173- int N = lz >> 9 ;
174- static const char charset [] = ".,-~:;!*=#$@" ;
175- printf ("\033[48;05;%dm%c\033[0m" , N / 4 + 1 ,
176- charset [N > 0 ? N < 12 ? N : 11 : 0 ]);
177- nnormals ++ ;
242+ }
243+ if (d < 2 ) {
244+ int N = lz >> 8 ;
245+ N = N > 0 ? N < 26 ? N : 25 : 0 ;
246+ setpixel (i , j , N );
178247 break ;
179248 }
180249
@@ -184,8 +253,9 @@ int main()
184253 * py += d*vyi14 >> 14;
185254 * pz += d*vzi14 >> 14;
186255 *
187- * idea is to make a 3d vector mul hw peripheral
188- * equivalent to this algorithm
256+ * Using a 3D fixed-point partial multiply approach, the
257+ * idea is to make a 3D vector multiplication hardware
258+ * peripheral equivalent to this algorithm.
189259 */
190260
191261 /* 11x1.14 fixed point 3x parallel multiply only 16 bit
@@ -196,14 +266,10 @@ int main()
196266 int16_t a = vxi14 , b = vyi14 , c = vzi14 ;
197267 while (d ) {
198268 if (d & 1024 ) {
199- dx += a ;
200- dy += b ;
201- dz += c ;
269+ dx += a , dy += b , dz += c ;
202270 }
203271 d = (d & 1023 ) << 1 ;
204- a >>= 1 ;
205- b >>= 1 ;
206- c >>= 1 ;
272+ a >>= 1 , b >>= 1 , c >>= 1 ;
207273 }
208274 /* We have already shifted down by 10 bits, so this
209275 * extracts the last four bits.
@@ -213,13 +279,21 @@ int main()
213279 pz += dz >> 4 ;
214280 }
215281
216- niters ++ ;
282+ n_iters ++ ;
217283 }
218284 }
219- puts ("" );
285+ if (j & 1 )
286+ printf ("\r\n" );
220287 }
221- printf ("%d iterations %d lit pixels\x1b[K" , niters , nnormals );
288+
289+ setcolors (25 , 33 );
290+ printf ("%6d iterations" , n_iters );
291+ setcolors (25 , 0 );
292+ printf ("\x1b[K" );
293+
294+ #if !defined(__riscv )
222295 fflush (stdout );
296+ #endif
223297
224298 /* Rotate sines, cosines, and their products to animate the torus
225299 * rotation about two axes.
@@ -231,10 +305,16 @@ int main()
231305 R (6 , cAcB , cAsB );
232306 R (6 , sAcB , sAsB );
233307
234- /* FIXME: Adjust tv_nsec to align with runtime expectations. */
235- struct timespec ts = {. tv_sec = 0 , . tv_nsec = 30000 } ;
236- nanosleep ( & ts , & ts );
308+ #if !defined( __riscv )
309+ usleep ( 15000 ) ;
310+ #endif
237311
238- printf ("\r\x1b[%dA" , (24 << RESY_SHIFT ) - 1 );
312+ printf ("\r\x1b[23A" );
313+ ++ frame ;
314+ prev_color1 = prev_color2 = -1 ;
239315 }
316+
317+ /* Show cursor again */
318+ printf ("\033[?25h" );
319+ return 0 ;
240320}
0 commit comments