2323 * Modified by Jim Huang <jserv@ccns.ncku.edu.tw>
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+ #define USE_MULTIPLIER 1
46+
47+ static const char * colormap [34 ] = {
48+ "0" , "8;5;232" , "8;5;233" , "8;5;234" , "8;5;235" , "8;5;236" , "8;5;237" ,
49+ "8;5;238" , "8;5;239" , "8;5;240" , "8;5;241" , "8;5;242" , "8;5;243" , "8;5;244" ,
50+ "8;5;245" , "8;5;246" , "8;5;247" , "8;5;248" , "8;5;249" , "8;5;250" , "8;5;251" ,
51+ "8;5;252" , "8;5;253" , "8;5;254" , "8;5;255" , "7" , "8;5;16" , "8;5;17" ,
52+ "8;5;18" , "8;5;19" , "8;5;20" , "8;5;21" , "8;5;22" , "8;5;23" ,
4453};
4554
55+ /* Previous background/foreground colors */
56+ static int prev_color1 = 0 , prev_color2 = 0 ;
57+
58+ static inline void setcolors (int fg /* foreground */ , int bg /* background */ )
59+ {
60+ printf ("\033[4%s;3%sm" , colormap [bg ], colormap [fg ]);
61+ }
62+
63+ static inline void setpixel (int x , int y , int color )
64+ {
65+ /* Stash the "upper" scanline so we can combine two rows of output. */
66+ static char scanline [80 ];
67+ int c1 , c2 ;
68+
69+ /* On even row (y & 1 == 0), just remember the color; no output yet. */
70+ if (!(y & 1 )) {
71+ scanline [x ] = color ;
72+ return ;
73+ }
74+
75+ /* On the odd row, pull the stored color from the previous row. */
76+ c1 = scanline [x ]; /* background */
77+ c2 = color ; /* foreground */
78+
79+ /* Same background/foreground: print a space with only background color */
80+ if (c1 == c2 ) {
81+ if (prev_color1 != c1 ) {
82+ printf ("\033[4%sm " , colormap [c1 ]);
83+ prev_color1 = c1 ;
84+ } else { /* Already set, just print a space */
85+ putchar (' ' );
86+ }
87+ return ;
88+ }
89+
90+ /* Different colors: print a block with new bg/fg if either changed */
91+ if (prev_color1 != c1 || prev_color2 != c2 ) {
92+ printf ("\033[4%s;3%sm" , colormap [c1 ], colormap [c2 ]);
93+ prev_color1 = c1 ;
94+ prev_color2 = c2 ;
95+ }
96+ printf ("\u2583" );
97+ }
98+
4699/* Torus radius and camera distance.
47100 * These values are closely tied to other constants, so modifying them
48101 * significantly may lead to unexpected behavior.
@@ -60,6 +113,12 @@ static const int dz = 5, r1 = 1, r2 = 2;
60113 x -= (y >> s); \
61114 y += (x >> s)
62115
116+ #if PRECISE
117+ #define N_CORDIC 10
118+ #else
119+ #define N_CORDIC 6
120+ #endif
121+
63122/* CORDIC algorithm used to calculate the magnitude of the vector |x, y| by
64123 * rotating the vector onto the x-axis. This operation also transforms vector
65124 * (x2, y2) accordingly, and updates the value of x2. This rotation is employed
@@ -68,87 +127,98 @@ static const int dz = 5, r1 = 1, r2 = 2;
68127 * noting that only one of the two lighting normal coordinates needs to be
69128 * retained.
70129 */
71- #define N_CORDIC 6
72130static int length_cordic (int16_t x , int16_t y , int16_t * x2_ , int16_t y2 )
73131{
74132 int x2 = * x2_ ;
75- if (x < 0 ) { // start in right half-plane
133+
134+ /* Move x into the right half-plane */
135+ if (x < 0 ) {
76136 x = - x ;
77137 x2 = - x2 ;
78138 }
139+
140+ /* CORDIC iterations */
79141 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- }
142+ int t = x , t2 = x2 ;
143+ int sign = (y < 0 ) ? -1 : 1 ;
144+
145+ x += sign * (y >> i );
146+ y -= sign * (t >> i );
147+ x2 += sign * (y2 >> i );
148+ y2 -= sign * (t2 >> i );
93149 }
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
150+
151+ /* Divide by ~0.625 (5/8) to approximate the 0.607 scaling factor
152+ * introduced by the CORDIC algorithm. See:
153+ * https://en.wikipedia.org/wiki/CORDIC
97154 */
98- * x2_ = (x2 >> 1 ) + (x2 >> 3 ) - (x2 >> 6 );
155+ * x2_ = (x2 >> 1 ) + (x2 >> 3 );
156+ #if PRECISE
157+ * x2_ -= x2 >> 6 ; /* get closer to 0.607 */
158+ #endif
159+
99160 return (x >> 1 ) + (x >> 3 ) - (x >> 6 );
100161}
101162
102163int main ()
103164{
165+ printf (
166+ "\033[48;5;16m" /* set background color black */
167+ "\033[38;5;15m" /* set foreground color white */
168+ "\033[H" /* move cursor home */
169+ "\033[?25l" /* hide cursor */
170+ "\033[2J" ); /* clear screen */
171+
172+ int frame = 1 ;
173+
104174 /* Precise rotation directions, sines, cosines, and their products */
105175 int16_t sB = 0 , cB = 16384 ;
106176 int16_t sA = 11583 , cA = 11583 ;
107177 int16_t sAsB = 0 , cAsB = 0 ;
108178 int16_t sAcB = 11583 , cAcB = 11583 ;
109179
110180 for (int count = 0 ; count < 500 ; count ++ ) {
111- /* This is a multiplication, but since dz is 5, it is equivalent to
181+ /* Starting position (p0).
182+ * This is a multiplication, but since dz is 5, it is equivalent to
112183 * (sb + (sb << 2)) >> 6.
113184 */
114185 const int16_t p0x = dz * sB >> 6 ;
115186 const int16_t p0y = dz * sAcB >> 6 ;
116187 const int16_t p0z = - dz * cAcB >> 6 ;
117188
118- const int r1i = r1 * 256 ;
119- const int r2i = r2 * 256 ;
189+ const int r1i = r1 * 256 , r2i = r2 * 256 ;
190+
191+ int n_iters = 0 ;
120192
121- int niters = 0 ;
122- int nnormals = 0 ;
123193 /* per-row increments
124194 * These can all be compiled into two shifts and an add.
125195 */
126- int16_t yincC = (12 * cA ) >> ( 8 + RESY_SHIFT );
127- int16_t yincS = (12 * sA ) >> ( 8 + RESY_SHIFT );
196+ int16_t yincC = (cA >> 6 ) + ( cA >> 5 ); /* 12*cA >> 8 */
197+ int16_t yincS = (sA >> 6 ) + ( sA >> 5 ); /* 12*sA >> 8 */
128198
129199 /* 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 );
200+ int16_t xincX = (cB >> 7 ) + ( cB >> 6 ); /* 6*cB >> 8 */
201+ int16_t xincY = (sAsB >> 7 ) + ( sAsB >> 6 ); /* 6* sAsB >> 8 */
202+ int16_t xincZ = (cAsB >> 7 ) + ( cAsB >> 6 ); /* 6* cAsB >> 8 */
133203
134204 /* 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;
205+ int16_t ycA = - ((cA >> 1 ) + (cA >> 4 )); /* -12 * yinc1 = -9*cA >> 4 */
206+ int16_t ysA = - ((sA >> 1 ) + (sA >> 4 )); /* -12 * yinc2 = -9*sA >> 4 */
137207
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;
208+ int xsAsB = (sAsB >> 4 ) - sAsB ; /* -40*xincY */
209+ int xcAsB = (cAsB >> 4 ) - cAsB ; /* -40*xincZ */
143210
211+ /* Render rows */
212+ for (int j = 0 ; j < 46 ; j ++ , ycA += yincC >> 1 , ysA += yincS >> 1 ) {
144213 /* 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 ) ;
214+ int16_t vxi14 = (cB >> 4 ) - cB - sB ; // -40* xincX - sB;
215+ int16_t vyi14 = ycA - xsAsB - sAcB ;
216+ int16_t vzi14 = ysA + xcAsB + cAcB ;
148217
149- for (int i = 0 ; i < ((80 << RESX_SHIFT ) - 1 );
218+ /* Render columns */
219+ for (int i = 0 ; i < 79 ;
150220 i ++ , vxi14 += xincX , vyi14 -= xincY , vzi14 += xincZ ) {
151- int t = 512 ; // (256 * dz) - r2i - r1i;
221+ int t = 512 ; /* Depth accumulation: (256 * dz) - r2i - r1i */
152222
153223 /* Assume t = 512, t * vxi >> 8 == vxi << 1 */
154224 int16_t px = p0x + (vxi14 >> 5 );
@@ -158,6 +228,7 @@ int main()
158228 int16_t ly0 = (sAcB - cA ) >> 2 ;
159229 int16_t lz0 = (- cAcB - sA ) >> 2 ;
160230 for (;;) {
231+ /* Distance from torus surface */
161232 int t0 , t1 , t2 , d ;
162233 int16_t lx = lx0 , ly = ly0 , lz = lz0 ;
163234 t0 = length_cordic (px , py , & lx , ly );
@@ -166,26 +237,27 @@ int main()
166237 d = t2 - r1i ;
167238 t += d ;
168239
169- if (t > 8 * 256 ) {
170- putchar (' ' );
240+ if (t > (8 * 256 )) {
241+ int N = (((j - frame ) >> 3 ) ^ (((i + frame ) >> 3 ))) & 1 ;
242+ setpixel (i , j , (N << 2 ) + 26 );
171243 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 ++ ;
244+ }
245+ if (d < 2 ) {
246+ int N = lz >> 8 ;
247+ N = N > 0 ? N < 26 ? N : 25 : 0 ;
248+ setpixel (i , j , N );
178249 break ;
179250 }
180251
252+ #ifdef USE_MULTIPLIER
253+ px += d * vxi14 >> 14 ;
254+ py += d * vyi14 >> 14 ;
255+ pz += d * vzi14 >> 14 ;
256+ #else
181257 {
182- /* equivalent to:
183- * px += d*vxi14 >> 14;
184- * py += d*vyi14 >> 14;
185- * pz += d*vzi14 >> 14;
186- *
187- * idea is to make a 3d vector mul hw peripheral
188- * equivalent to this algorithm
258+ /* Using a 3D fixed-point partial multiply approach, the
259+ * idea is to make a 3D vector multiplication hardware
260+ * peripheral equivalent to this algorithm.
189261 */
190262
191263 /* 11x1.14 fixed point 3x parallel multiply only 16 bit
@@ -196,14 +268,10 @@ int main()
196268 int16_t a = vxi14 , b = vyi14 , c = vzi14 ;
197269 while (d ) {
198270 if (d & 1024 ) {
199- dx += a ;
200- dy += b ;
201- dz += c ;
271+ dx += a , dy += b , dz += c ;
202272 }
203273 d = (d & 1023 ) << 1 ;
204- a >>= 1 ;
205- b >>= 1 ;
206- c >>= 1 ;
274+ a >>= 1 , b >>= 1 , c >>= 1 ;
207275 }
208276 /* We have already shifted down by 10 bits, so this
209277 * extracts the last four bits.
@@ -212,14 +280,23 @@ int main()
212280 py += dy >> 4 ;
213281 pz += dz >> 4 ;
214282 }
283+ #endif
215284
216- niters ++ ;
285+ n_iters ++ ;
217286 }
218287 }
219- puts ("" );
288+ if (j & 1 )
289+ printf ("\r\n" );
220290 }
221- printf ("%d iterations %d lit pixels\x1b[K" , niters , nnormals );
291+
292+ setcolors (25 , 33 );
293+ printf ("%6d iterations" , n_iters );
294+ setcolors (25 , 0 );
295+ printf ("\x1b[K" );
296+
297+ #if !defined(__riscv )
222298 fflush (stdout );
299+ #endif
223300
224301 /* Rotate sines, cosines, and their products to animate the torus
225302 * rotation about two axes.
@@ -231,10 +308,16 @@ int main()
231308 R (6 , cAcB , cAsB );
232309 R (6 , sAcB , sAsB );
233310
234- /* FIXME: Adjust tv_nsec to align with runtime expectations. */
235- struct timespec ts = {. tv_sec = 0 , . tv_nsec = 30000 } ;
236- nanosleep ( & ts , & ts );
311+ #if !defined( __riscv )
312+ usleep ( 15000 ) ;
313+ #endif
237314
238- printf ("\r\x1b[%dA" , (24 << RESY_SHIFT ) - 1 );
315+ printf ("\r\x1b[23A" );
316+ ++ frame ;
317+ prev_color1 = prev_color2 = -1 ;
239318 }
319+
320+ /* Show cursor again */
321+ printf ("\033[?25h" );
322+ return 0 ;
240323}
0 commit comments