We now need to map the 3D point (x,y,z) onto a 2D terminal, i.e. find (x′,y′).
Terminal coordinates increase rightward and downward. (0,0) is at its top-left corner. Denoting its width and height with W and H, the center of the terminal is (W/2,H/2).
First, move the torus a fixed distance d away from the viewer. The point's coordinates become (x,y,z′), where
z′=d+z
Choose d large enough that z′>0 for every point.
Now place an imaginary screen in front of the viewer. Let K be its distance from the viewer, measured in terminal-cell units. Draw a straight line from the viewer through the point. Where that line crosses the screen is where we draw the point.
u = K · x / z′ = 11 · 20 / 44 = 5.00 · drag the point or the screen
Let u and v be its horizontal and vertical offsets from the terminal's center, also measured in cells. Similar triangles give
Ku=z′x,Kv=z′y.
Therefore,
u=Kz′x,v=Kz′y.
Adding the terminal's center and accounting for rows increasing downward gives
x′=2W+Kz′x,y′=2H−Kz′y
K controls how large the torus appears on the terminal. Larger values make the torus appear larger.
The 1/z′ factor is what creates perspective: points farther from the viewer appear closer to the center and therefore smaller.
3. Compute brightness
Suppose we choose a light with direction
l=(0,1,−1),
which points upward and toward the viewer.
The mechanics
To determine the brightness at each point on the torus, we look at the angle between the light source and its surface normal, or the direction perpendicular to the surface:
(cosθ,sinθ,0).
This is the normal before rotation. We apply the same rotations to it as we did to the point, and write the rotated normal as
n=(Nx,Ny,Nz),
The angle α between the normal and the light source tells us how directly the surface faces the light. We use the dot product as a brightness score, or luminance L:
L=n⋅l=∥n∥∥l∥cosα.
Here, the normal has length 1, and the light has length 2, so
L=n⋅l=2cosα,
L changes with cosα:
α=0∘: the surface faces the light directly; the value is largest.
α=90∘: the light runs along the surface; the value is 0.
α>90∘: the surface faces away from the light; the value is negative, which we treat as unlit.
Since −1≤cosα≤1, we have
−2≤L≤2.
We ignore points with L≤0, then map positive values of L to increasingly bright ASCII characters.
The math
We have
L=n⋅l=Nx⋅0+Ny⋅1+Nz⋅(−1)=Ny−Nz.
To expand this, apply the same three rotations to the original normal. Below, xi, yi, and zi denote the normal's components at stage i, rather than the point's coordinates. Start with
(x0,y0,z0)=(cosθ,sinθ,0).
1. Rotate by ϕ around y. The y-component stays fixed, while x and z mix. Using the rotation convention in our matrix,