cube/main.c
2025-04-27 18:19:47 -04:00

274 lines
6.1 KiB
C

#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <fcntl.h>
#define SCREEN_WIDTH 100
#define SCREEN_HEIGHT 50
#define CUBE_WIDTH 10
#define FACE_FRONT '#'
#define FACE_BACK '0'
#define FACE_BOTTOM '!'
#define FACE_LEFT '%'
#define FACE_RIGHT '@'
#define FACE_TOP '-'
#define STEP 0.2
#define ACTION_STEP 0.1
#define PITCH_STEP 0.08
#define ROLL_STEP 0.05
#define YAW_STEP 0.05
#define K1 100
#define K2 50
#define _RESET_ANGLE(input) do {\
Angle.currentTarget = 0;\
input = 0; \
} while (0);
#define RESET_A_ANGLE(input) do {\
Angle.A = Angle.ATarget; \
_RESET_ANGLE(input); \
} while (0);
#define RESET_B_ANGLE(input) do {\
Angle.B = Angle.BTarget; \
_RESET_ANGLE(input); \
} while (0);
#define RESET_C_ANGLE(input) do {\
Angle.C = Angle.CTarget; \
_RESET_ANGLE(input); \
} while (0);
struct {
double A, B, C;
double ATarget, BTarget, CTarget;
char currentTarget;
double cosA, sinA;
double cosB, sinB;
double cosC, sinC;
} Angle;
void initAngles() {
Angle.A = 0, Angle.B = 0, Angle.C = 0;
Angle.ATarget = 0, Angle.BTarget = 0, Angle.CTarget = 0;
Angle.currentTarget = 0;
}
double zBuffer[SCREEN_HEIGHT * SCREEN_WIDTH];
char output[SCREEN_HEIGHT * SCREEN_WIDTH];
static volatile char shouldBreak = 1;
static struct termios originalTerm = {};
double calcX(double x, double y, double z){
return
x * Angle.cosC * Angle.cosB +
y * Angle.sinC * Angle.cosB
-z * Angle.sinB;
}
double calcY(double x, double y, double z){
return
x * (Angle.cosC * Angle.sinB * Angle.sinA - Angle.sinC * Angle.cosA) +
y * (Angle.sinC * Angle.sinB * Angle.sinA + Angle.cosC * Angle.cosA) +
z * Angle.cosB * Angle.sinA;
}
double calcZ(double x, double y, double z){
return
x * (Angle.cosC * Angle.sinB * Angle.cosA + Angle.sinC * Angle.sinA) +
y * (Angle.sinC * Angle.sinB * Angle.cosA - Angle.cosC * Angle.sinA) +
z * Angle.cosB * Angle.cosA;
}
void calcAngle(){
Angle.cosA = cos(Angle.A);
Angle.sinA = sin(Angle.A);
Angle.cosB = cos(Angle.B);
Angle.sinB = sin(Angle.B);
Angle.cosC = cos(Angle.C);
Angle.sinC = sin(Angle.C);
}
char *chooseColor(char c){
switch(c) {
case '#':
return "\033[31;1;4m#\033[0m";
case '0':
return "\033[32;1;4m0\033[0m";
case '!':
return "\033[33;1;4m!\033[0m";
case '%':
return "\033[34;1;4m%\033[0m";
case '@':
return "\033[35;1;4m@\033[0m";
case '-':
return "\033[36;1;4m-\033[0m";
case ' ':
return " ";
default: // unused, but issues no warning
return "";
}
}
void printAscii() {
#ifdef __APPLE__
fcntl(STDOUT_FILENO, F_SETFL, ~O_NONBLOCK);
#endif
for (int k = 0 ; k < SCREEN_WIDTH * SCREEN_HEIGHT ; ++k)
printf("%s", k % SCREEN_WIDTH ? chooseColor(output[k]) : "\r\n");
printf("\r\n");
#ifdef __APPLE__
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
#endif
}
void rotateCube(double cubeX, double cubeY, double cubeZ, char ch){
calcAngle();
double x = calcX(cubeX, cubeY, cubeZ);
double y = calcY(cubeX, cubeY, cubeZ);
double invZ = 1 / (calcZ(cubeX, cubeY, cubeZ) + K2);
int screenX = (int)(SCREEN_WIDTH / 2) + floor((x) * K1) * invZ;
int screenY = (int)(SCREEN_HEIGHT / 2) + floor(((y) * K1 / 2 ) * invZ);
// TODO luminescence
if (screenX > SCREEN_WIDTH || screenX < 0) return;
int idx = screenY * SCREEN_WIDTH + screenX;
if(idx >= 0 && idx < SCREEN_WIDTH * SCREEN_HEIGHT)
if(zBuffer[idx] < invZ) {
zBuffer[idx] = invZ;
output[idx] = ch;
}
}
void intHandler(int unused) {
shouldBreak = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &originalTerm);
}
void printBanner(char keyboardInput) {
printf("Last input character: %c", keyboardInput);
}
void handleAngle(char *input) {
if (!Angle.currentTarget) {
Angle.currentTarget = *input;
switch (*input) {
case 'W':
Angle.ATarget = Angle.A - M_PI_2;
break;
case 'A':
Angle.BTarget = Angle.B + M_PI_2;
break;
case 'S':
Angle.ATarget = Angle.A + M_PI_2;
break;
case 'D':
Angle.BTarget = Angle.B - M_PI_2;
break;
default:
Angle.currentTarget = 0;
}
// idle animation
//Angle.A += ROLL_STEP;
Angle.B += YAW_STEP;
//Angle.C += PITCH_STEP;
}
else
switch(Angle.currentTarget) {
case 'W':
Angle.A -= ACTION_STEP;
if (Angle.A <= Angle.ATarget) RESET_A_ANGLE(*input);
break;
case 'A':
Angle.B += ACTION_STEP;
if (Angle.B >= Angle.BTarget) RESET_B_ANGLE(*input);
break;
case 'S':
Angle.A += ACTION_STEP;
if (Angle.A >= Angle.ATarget) RESET_A_ANGLE(*input);
break;
case 'D':
Angle.B -= ACTION_STEP;
if (Angle.B <= Angle.BTarget) RESET_B_ANGLE(*input);
break;
}
}
char getInput(char *keyboardInput) {
char c = getchar();
if (c == '\033') { // if the first value is esc
if ((c = getchar()) == '[')
switch(c = getchar()) { // the real value
case 'A':
c = 'W';
break;
case 'B':
c = 'S';
break;
case 'C':
c = 'D';
break;
case 'D':
c = 'A';
break;
}
}
if (c != EOF)
*keyboardInput = c;
while((c = getchar()) != '\n' && c != EOF) {} // clean stdin
handleAngle(keyboardInput);
return c;
}
int main(){
char keyboardInput = 0;
signal(SIGINT, intHandler);
tcgetattr(STDIN_FILENO, &originalTerm);
#ifndef __APPLE__
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
#endif
struct termios t = {};
cfmakeraw(&t);
t.c_lflag |= ISIG;
t.c_cc[VINTR] = 3;
tcsetattr(STDIN_FILENO, TCSANOW, &t);
initAngles();
while(shouldBreak) {
memset(output, ' ', SCREEN_WIDTH * SCREEN_HEIGHT);
memset(zBuffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(double));
for(double cubeX = -CUBE_WIDTH + STEP ; cubeX < CUBE_WIDTH - STEP ; cubeX += STEP) {
for(double cubeY = -CUBE_WIDTH + STEP ; cubeY < CUBE_WIDTH - STEP ; cubeY += STEP) {
rotateCube(cubeX, cubeY, -CUBE_WIDTH, FACE_FRONT);//front
rotateCube(cubeX, cubeY, CUBE_WIDTH, FACE_BACK);//back
rotateCube(CUBE_WIDTH, cubeX, cubeY, FACE_RIGHT);//right
rotateCube(-CUBE_WIDTH, cubeX, cubeY, FACE_LEFT);//left
rotateCube(cubeX, CUBE_WIDTH, cubeY, FACE_BOTTOM);//bottom
rotateCube(cubeX, -CUBE_WIDTH, cubeY, FACE_TOP);//top
}
}
printf("\x1b[2J");
printBanner(keyboardInput);
printAscii();
getInput(&keyboardInput);
usleep(100000);
}
}