Skip to content

Commit 043df30

Browse files
committed
tests: donut: Enhance ANSI graphics
This variant requires no memory, floating-point arithmetic, trigonometric functions, square roots, divisions, or even a multiplier (though having one simplifies the design). It is intended for minimal implementations. Reducing the number of CORDIC iterations in the inner loop can produce a faceted appearance but also introduces larger rendering errors. The ANSI "pseudo-graphics" work was initially done by Bruno Levy.
1 parent ee3bd57 commit 043df30

File tree

1 file changed

+158
-75
lines changed

1 file changed

+158
-75
lines changed

Diff for: tests/donut.c

+158-75
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
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,
@@ -35,14 +35,67 @@
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
72130
static 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

102163
int 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

Comments
 (0)