commit b19ea4b759391dcbe911f4da4b9df2c8d468907b Author: Arkaprabha Chakraborty Date: Wed Jan 5 22:58:39 2022 +0530 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9abc54b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Executables +*.out diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f1c8967 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Arkaprabha Chakraborty + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..12602e0 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +deb: + @sudo apt install gcc python3 + +arch: + @sudo pacman -S gcc python3 + +c: doughnut.c + @gcc -o doughnut.out doughnut.c -lm + @./doughnut.out + +py: doughnut.py + @python3 doughnut.py + +clean: doughnut.out + @rm doughnut.out diff --git a/README.md b/README.md new file mode 100755 index 0000000..dff8c6a --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Description +These programs print a spinning ASCII torus (mathematical name for a doughnut-shaped object) on the terminal. Original post by Andy Sloane can be found [here](https://www.a1k0n.net/2011/07/20/donut-math.html). + +# For Linux +## Prerequisites for Debian based distros +* `sudo apt install make` to install Make +* `make deb` to install the required compilers +## Prerequisites for Arch based distros +* `sudo pacman -S make` to install Make +* `make arch` to install the required compilers + +## How to run? +* Clone or download the repository +* Open a terminal inside the project directory +* Complete the prerequisites for your OS +* `make c` to run the C version +* `make py` to run the Python version + +## How to clean up the executable? +* `make clean` to delete the executable + +# Screenshot +![screenshot](https://github.com/arkorty/Spinning-ASCII-Torus/blob/main/blob/screenshot.png) diff --git a/blob/screenshot.png b/blob/screenshot.png new file mode 100644 index 0000000..4fb2345 Binary files /dev/null and b/blob/screenshot.png differ diff --git a/doughnut.c b/doughnut.c new file mode 100644 index 0000000..47e522e --- /dev/null +++ b/doughnut.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include + +#define clear_terminal() printf("\e[1;1H\e[2J"); // Clears the terminal + +// Function allocates memory for the frame buffer +char **allocate_memory(int size) { + char **array = malloc(size * sizeof(char *)); + + for (int i = 0; i < size; i++) + array[i] = malloc(size * sizeof(char)); + + return array; +} + +// Function gets the size of the terminal +int get_terminal_size() { + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + int win_row_size = (int)w.ws_row; + int win_col_size = (int)w.ws_col; + + if (win_row_size < win_col_size) + return win_row_size; + else + return win_col_size; +} + +// Function dumps the frame into the terminal +void dump_frame(char **frame, int size) { + for (int i = 0; i < size; i++) + printf("%s\n", frame[i]); +} + +// Function builds the frame and returns the frame buffer +char **build_frame(char **frame, int frame_num, int size, int K) { + float x = frame_num * 0.0093; // Rotational speed around the x axis + float y = frame_num * 0.0048; // Rotational speed around the y axis + + float cos_x = cos(x), sin_x = sin(x); // Precomputing sines and cosines of x + float cos_y = cos(y), sin_y = sin(y); // Precomputing sines and cosines of y + + float z_buffer[size][size]; // Declaring buffer for storing z coordinates + + // Initializing frame buffer and z buffer + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) { + frame[i][j] = ' '; + z_buffer[i][j] = 0; + } + + // Loop uses theta to revolve a point around the center of the circle + // 6.283186 = 2 * Pi = 360° + for (float theta = 0; theta < 6.283186; theta += 0.031) { + // Precomputing sines and cosines of theta + float cos_theta = cos(theta), sin_theta = sin(theta); + + // Loop uses phi to revolve the circle around the center of the torus + for (float phi = 0; phi < 6.283186; phi += 0.016) { + // Precomputing sines and cosines of phi + float cos_phi = cos(phi), sin_phi = sin(phi); + + // Calculating the x and y coordinates of the circle before the + // revolution + float circle_x = 2 + 1 * cos_theta, circle_y = 1 * sin_theta; + + // Calculating the x and y coordinates after the revolution + float x = circle_x * (cos_y * cos_phi + sin_x * sin_y * sin_phi) - + circle_y * cos_x * sin_y; + float y = circle_x * (sin_y * cos_phi - sin_x * cos_y * sin_phi) + + circle_y * cos_x * cos_y; + + // Calculating z + float z = 5 + cos_x * circle_x * sin_phi + circle_y * sin_x; + + // Calculating the inverse of z + float z_inv = 1 / z; + + // Calculating x and y coordinates of the 2D projection + int x_proj = size / 2 + K * z_inv * x; + int y_proj = size / 2 - K * z_inv * y; + + // Calculating luminous intensity + float lumi_int = + cos_phi * cos_theta * sin_y - cos_x * cos_theta * sin_phi - + sin_x * sin_theta + + cos_y * (cos_x * sin_theta - cos_theta * sin_x * sin_phi); + + /* Checking if surface is pointing away from the point of view + * Also checking if the point is closer than any other point + * previously plotted + */ + if (lumi_int > 0 && z_inv > z_buffer[x_proj][y_proj]) { + z_buffer[x_proj][y_proj] = z_inv; + + // Bringing the value of luminance between 0 to 11 + int lumi_idx = lumi_int * 8; + + /* Storing an appropriate character that represents the correct + * amount of luminance + */ + frame[x_proj][y_proj] = ".,-~:;=!*#$@"[lumi_idx]; + } + } + } + + // Returning the frame buffer + return frame; +} + +int main() { + // Getting the size of the terminal + const int size = get_terminal_size(); + + // Allocating memory to the frame buffer + char **frame = allocate_memory(size); + + // Loop rotates the torus around both the axes + for (int frame_num = 0; true; frame_num++) { + + // Building and dumping the frame into the terminal + int konst = size * 5 * 3 / (8 * (1 + 2)); + dump_frame(build_frame(frame, frame_num, size, konst), size); + + // Clears the screen + clear_terminal(); + } + + return 0; +} diff --git a/doughnut.py b/doughnut.py new file mode 100644 index 0000000..8825d4f --- /dev/null +++ b/doughnut.py @@ -0,0 +1,119 @@ +from os import name, system, get_terminal_size +from math import sin, cos, pi + + +# Screen dimensions +screen_width = screen_height = ( + get_terminal_size().lines + if get_terminal_size().lines < get_terminal_size().columns + else get_terminal_size().columns +) + +# Jump values for theta and phi +theta_jump = 0.07 +phi_jump = 0.02 + +R1 = 1 +R2 = 2 +K2 = 5 + +# Calculate K1 based on screen size: The maximum x-distance occurs roughly at +# the edge of the torus, which is at x=R1+R2, z=0. we want that to be +# displaced 3/8ths of the width of the screen, which is 3/4th of the way from +# the center to the side of the screen. We also substract 2 from the +# screen_width to introduce a border. +# screen_width-2*K2*3/(8*(R1+R2)) = K1 +K1 = (screen_width - 2) * K2 * 3 / (8 * (R1 + R2)) + + +def render_frame(A, B): + + # Precomputing sines an cosines + cosA = cos(A) + sinA = sin(A) + cosB = cos(B) + sinB = sin(B) + + # Creating 2D arrays + output = [] + zbuffer = [] + + # Initializing 2D arrays + for i in range(screen_height): + output.append([" "] * (screen_width)) + zbuffer.append([0] * (screen_width)) + + # Value of theta increases until it completes a rotation around the + # cross-sectional circle of the torus + theta = 0 + while theta < 2 * pi: + + # Precomputing sines and cosines of theta + costheta = cos(theta) + sintheta = sin(theta) + + # Value of phi increases until the circle completes a revolution around + # the center of the torus + phi = 0 + while phi < 2 * pi: + + # Precomputing sines and cosines of phi + cosphi = cos(phi) + sinphi = sin(phi) + + # (x, y) coordinates of the circle before revolution + circlex = R2 + R1 * costheta + circley = R1 * sintheta + + # Final 3D coordinates after revolution + x = circlex * (cosB * cosphi + sinA * sinB * sinphi) - circley * cosA * sinB + y = circlex * (sinB * cosphi - sinA * cosB * sinphi) + circley * cosA * cosB + z = K2 + cosA * circlex * sinphi + circley * sinA + + # Inverse of z + zinv = 1 / z + + # (x, y) coordinates of the 2D projection + xp = int(screen_width / 2 + K1 * zinv * x) + yp = int(screen_height / 2 - K1 * zinv * y) + + # Calculating luminance + L = cosphi * costheta * sinB - cosA * costheta * sinphi - sinA * sintheta + +cosB * (cosA * sintheta - costheta * sinA * sinphi) + + # L ranges from -sqrt(2) to +sqrt(2). If it's < 0, the surface + # is pointing away from us, so we won't bother trying to plot it. + if L > 0 and zinv > zbuffer[xp][yp]: + + # Test against the z-buffer. Larger 1/z means that the pixel is + # closer to the viewer than what's already plotted. + + zbuffer[xp][yp] = zinv + luminance_index = int(L * 8) + + # Now the luminance_index is in the range 0..11 (8*sqrt(2) + # = 11.3) now we lookup the character corresponding to the + # luminance and plot it in our output. + output[xp][yp] = ".,-~:;=!*#$@"[luminance_index] + + phi += phi_jump + + theta += theta_jump + + # Clearing old frame and printing new frame + system("cls") if name is "nt" else system("clear") + for i in range(screen_width): + for j in range(screen_height): + print(output[i][j], end="") + print() + + +if __name__ == "__main__": + A = 0 + B = 0 + + # Calling render_frame() n times + while True: + render_frame(A, B) + A += 0.07 + B += 0.03