Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Executables
|
||||||
|
*.out
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal 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
15
Makefile
Executable 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
23
README.md
Executable 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
|
||||||
|

|
||||||
BIN
blob/screenshot.png
Normal file
BIN
blob/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
135
doughnut.c
Normal file
135
doughnut.c
Normal 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
119
doughnut.py
Normal 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
|
||||||
Reference in New Issue
Block a user