intmain() { float A = 0, B = 0; float i, j; int k; float z[1760]; char b[1760]; printf("\x1b[2J"); for(;;) { memset(b, 32, 1760); memset(z, 0, 7040); for(j = 0; j < 6.28; j += 0.07) { for(i = 0; i < 6.28; i += 0.02) { float c = sin(i); float d = cos(j); float e = sin(A); float f = sin(j); float g = cos(A); float h = d + 2; float D = 1 / (c * h * e + f * g + 5); float l = cos(i); float m = cos(B); float n = sin(B); float t = c * h * g - f * e; int x = 40 + 30 * D * (l * h * m - t * n); int y = 12 + 15 * D * (l * h * n + t * m); int o = x + 80 * y; int N = 8 * ((f * e - c * d * g) * m - c * d * e - f * g - l * d * n); if(22 > y && y > 0 && x > 0 && 80 > x && D > z[o]) { z[o] = D; b[o] = ".,-~:;=!*#$@"[N > 0 ? N : 0]; } } } printf("\x1b[H"); for(k = 0; k < 1761; k++) { putchar(k % 80 ? b[k] : 10); A += 0.00004; B += 0.00002; } usleep(30000); } return0; }
float c = sin(i); float d = cos(j); float e = sin(A); float f = sin(j); float g = cos(A); float h = d + 2;
把常用的 sin/cos 抽出来存到变量,减少重复计算(可读性变差但更短)。
c = sin(i), l = cos(i) (后面 l 出现)
d = cos(j), f = sin(j)
e = sin(A), g = cos(A)
h = d + 2 —— 这里 h 对应几何中 R₂ + R₁ * cos(θ) 的简化(在原实现中 R₁=1, R₂=2)。也就是把环面"从原点移开"的那一项。
深度、旋转与投影
1 2 3 4 5 6 7 8
float D = 1 / (c * h * e + f * g + 5); float l = cos(i); float m = cos(B); float n = sin(B); float t = c * h * g - f * e; int x = 40 + 30 * D * (l * h * m - t * n); int y = 12 + 15 * D * (l * h * n + t * m); int o = x + 80 * y;
o = x + 80*y:把二维坐标映射成一维缓冲索引(行优先)。在使用 o 之前代码用条件保证 o 在有效范围内。
亮度计算与写入缓冲
1 2 3 4 5
int N = 8 * ((f * e - c * d * g) * m - c * d * e - f * g - l * d * n); if(22 > y && y > 0 && x > 0 && 80 > x && D > z[o]) { z[o] = D; b[o] = ".,-~:;=!*#$@"[N > 0 ? N : 0]; }
N 是亮度索引(整数),等于大致 8 * (L),其中 ( ... ) 对应法向量与光照方向的点积(即亮度 L 的表达式)。乘 8 是为了把 -1…1 的亮度拉伸到索引范围。
ASCII 字符串 ".,-~:;=!*#$@" 按从暗到亮排序,用 N 作为索引选择字符。
条件 22 > y && y > 0 && x > 0 && 80 > x:判断屏幕范围(注意这里是严格 > 和 <,会排除边界 0 与 22/80,这是实现上的小瑕疵,但不会影响整体效果)
使用 D > z[o]:因为 z 保存的是以前写入位置的 inverse-depth,D 越大表示越近(depth 小),所以 D > z[o] 表示当前点更靠近观察者,应覆盖旧像素。
输出与旋转步进
1 2 3 4 5 6 7
printf("\x1b[H"); for(k = 0; k < 1761; k++) { putchar(k % 80 ? b[k] : 10); A += 0.00004; B += 0.00002; } usleep(30000);
// sample torus surface for (float j = 0.0f; j < 2.0f * M_PI; j += 0.07f) { for (float i = 0.0f; i < 2.0f * M_PI; i += 0.02f) { // precompute sines/cosines float sin_i = sinf(i), cos_i = cosf(i); float sin_j = sinf(j), cos_j = cosf(j); float sinA = sinf(A), cosA = cosf(A); float sinB = sinf(B), cosB = cosf(B);
// geometry constants (R1 = 1, R2 = 2) float h = cos_j + 2.0f; // R2 + R1 * cos(theta) float t = sin_i * h * cosA - sin_j * sinA;
// inverse depth (1/(z + K2)), K2 = 5 float D = 1.0f / (sin_i * h * sinA + sin_j * cosA + 5.0f);
// screen projection int x = (int)(40 + 30 * D * (cos_i * h * cosB - t * sinB)); int y = (int)(12 + 15 * D * (cos_i * h * sinB + t * cosB)); int o = x + 80 * y;
if (x >= 0 && x < 80 && y >= 0 && y < 22) { if (D > z[o]) { z[o] = D;
// luminance calculation (scaled to roughly 0..11) int N = (int)(8.0f * ( (sin_j * sinA - sin_i * cos_j * cosA) * cosB - sin_i * cos_j * sinA - sin_j * cosA - cos_i * cos_j * sinB ));
// clamp N to charset index range if (N < 0) N = 0; if (N > 11) N = 11;
b[o] = charset[N]; } } } }
// move cursor to home and print buffer line by line printf("\x1b[H"); for (int row = 0; row < 22; ++row) { int base = row * 80; for (int col = 0; col < 80; ++col) { putchar(b[base + col]); } putchar('\n'); }
// update rotation angles (per frame) A += 0.0704f; // equals 0.00004 * 1760 in original B += 0.0352f; // equals 0.00002 * 1760 in original