Initial commit

This commit is contained in:
Arkaprabha Chakraborty
2022-01-05 22:58:39 +05:30
commit b19ea4b759
7 changed files with 315 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Executables
*.out

21
LICENSE Normal file
View File

@@ -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.

15
Makefile Executable file
View File

@@ -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

23
README.md Executable file
View File

@@ -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)

BIN
blob/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

135
doughnut.c Normal file
View File

@@ -0,0 +1,135 @@
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#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;
}

119
doughnut.py Normal file
View File

@@ -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