Donut.js
Donut.js

Donut.js

Published
Author
2006 年 Andy Sloane 发布了他的 donut.c 代码,代码在控制台输出一个由 ASCII 字符组成的旋转的甜甜圈,代码也格式化成甜甜圈的形状,代码长这样:
k;double sin() ,cos();main(){float A= 0,B=0,i,j,z[1760];char b[ 1760];printf("\x1b[2J");for(;; ){memset(b,32,1760);memset(z,0,7040) ;for(j=0;6.28>j;j+=0.07)for(i=0;6.28 >i;i+=0.02){float c=sin(i),d=cos(j),e= sin(A),f=sin(j),g=cos(A),h=d+2,D=1/(c* h*e+f*g+5),l=cos (i),m=cos(B),n=s\ in(B),t=c*h*g-f* e;int x=40+30*D* (l*h*m-t*n),y= 12+15*D*(l*h*n +t*m),o=x+80*y, 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;1761>k;k++) putchar(k%80?b[k]:10);A+=0.04;B+= 0.02;}}/*****####*******!!=;:~ ~::==!!!**********!!!==::- .,~~;;;========;;;:~-. ..,--------,*/
donut.c
这样的代码想要看懂是很难的,Andy 也在后续的文章中详细解释了原理,本文将参考那篇文章实现相同的效果,并解释是怎么做到的。

建立坐标系

要绘制空间中的一个物体,首先要有一个三维坐标系。下面是最常见的三维笛卡尔坐标系,空间中的一个点用 表示。

建模

现在坐标系中还什么都没有,要怎么描述空间中的一个圆环呢?想象现在 平面上有一个圆,如果将其绕 轴旋转 即可得到一个圆环(甜甜圈),这样就可以得到甜甜圈表面的点的集合。

绘制 XY 平面的圆

绘制 为圆心, 为半径的圆。圆上的点的坐标:
绕圆心旋转角度 Θ
绕圆心旋转角度 Θ
其中 为旋转角,范围是 ,即旋转一周,可得到圆上的点。

绘制圆环

将圆上的点绕 轴旋转 ,令旋转角为 ,这样就可以得到一个 3D 的圆环。 绕某个轴旋转可以将向量于旋转矩阵相乘:
绕 X 轴旋转角度 Φ
绕 X 轴旋转角度 Φ
其中 是上面步骤中计算出的圆上的点。现在有了圆环表面的点的坐标,但是它们在每一帧里都是相同的,这样甜甜圈是静止的。要让甜甜圈动起来,让其绕 轴和 轴旋转,并在不同帧里更新点的坐标,这样甜甜圈就动了起来。令绕 轴的旋转角为 ,绕 轴的旋转角为 ,和上面的旋转类似:
其中的是上一步绕 轴旋转后得到的点,将其带入:
现在式子里的 第一步得到的,值是 ,将其带入:
式子看起来很复杂,我们可以提前计算一些常用表达式,在代码中重用它们。当然,你也可以分步计算,就像我们前面那样,可以减少一些复杂度,并且利于调试。
// 绘制圆 let [x, y, z] = [r1 * sinT, r2 + r1 * cosT, 0]; // 绘制圆环 [x, y, z] = [x, y * cosP - z * sinP, y * sinP + z * cosP]; // 绕 Y 轴旋转 A [x, y, z] = [x * cosA + z * sinA, y, z * cosA - x * sinA]; // 绕 Z 轴旋转 B [x, y, z] = [x * cosB - y * sinB, x * sinB + y * cosB, z];

成像(投影)

现在甜甜圈还没有出现在屏幕上,要在屏幕上显示甜甜圈,需要将其投影到屏幕上。
假设 平面是我们观察的屏幕,