cube/main.c
violette 4f8fde9eb5 fix: now working on glibc
fixed a bug were stdin was set to nonblocking and never reset back to blocking.
This caused STDOUT to become nonblock, and for some reason glibc cannot deal
with it, but every other libc could. uh.
2025-05-13 00:48:24 -04:00

598 lines
15 KiB
C

/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 43):
* vi@violette.town wrote this file. You can do whatever you want with this
* stuff. If we meet some day, and you think this stuff is worth it,
* you can buy me a beer in return. Violette Paulin
* ----------------------------------------------------------------------------
*/
#include <sys/ioctl.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <fcntl.h>
#include <float.h>
#include "json.h"
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 32
#define CUBE_WIDTH 10
enum faces {
FACE_FRONT = 0,
FACE_LEFT,
FACE_RIGHT,
FACE_BOTTOM,
FACE_TOP,
FACE_BACK,
NUM_FACES,
};
#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 SQ(n) n * n
#define GET_ROTATE_X_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.x = sin(_a/2); q; })
#define GET_ROTATE_Y_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.y = sin(_a/2); q; })
#define GET_ROTATE_Z_Q(a) ({ double _a = (a) ; struct Quaternions q = {}; q.w = cos(_a/2); q.z = sin(_a/2); q; })
//TODO Idle animations
#define IS_IDLE (Idle.x || Idle.y || Idle.z)
#define RESET_IDLE {Idle.x = 0; Idle.y = 0; Idle.z = 0;}
struct {
char x;
char y;
char z;
} Idle;
struct Quaternions {
double w;
double x;
double y;
double z;
} Target, Current, Last;
double interpolationStep = 0;
double zBuffer[SCREEN_HEIGHT * SCREEN_WIDTH];
char output[SCREEN_HEIGHT * SCREEN_WIDTH];
#ifdef VBUF
char buf[SCREEN_HEIGHT * SCREEN_WIDTH * 7 * 2]; // stdout buffer
#endif
static volatile char shouldBreak = 1;
static volatile char currentlyMoving = 0;
static struct termios originalTerm = {};
void
normalize(struct Quaternions *q)
{
double n = sqrt(SQ(q->w) + SQ(q->x) + SQ(q->y) + SQ(q->z));
if (n == 0)
return;
q->w /= n;
q->x /= n;
q->y /= n;
q->z /= n;
}
struct Quaternions
mult(struct Quaternions q, double x, double y, double z)
{
//p = q * p * qbar
struct Quaternions res;
res.w = 0;
res.x = x * (SQ(q.w) + SQ(q.x) - SQ(q.y) - SQ(q.z))
+ 2 * y * (q.x * q.y - q.w * q.z)
+ 2 * z * (q.x * q.z + q.w * q.y);
res.y = 2 * x * (q.x * q.y + q.w * q.z)
+ y * (SQ(q.w) - SQ(q.x) + SQ(q.y) - SQ(q.z))
+ 2 * z * (q.y * q.z - q.w * q.x);
res.z = 2 * x * (q.x * q.z - q.w * q.y)
+ 2 * y * (q.y * q.z + q.w * q.x)
+ z * (SQ(q.w) - SQ(q.x) - SQ(q.y) + SQ(q.z));
return res;
}
struct Quaternions
multQ(struct Quaternions p, struct Quaternions q)
{
if (p.x <= 0.001 && p.x >= -0.001
&& p.y <= 0.001 && p.y >= -0.001
&& p.z <= 0.001 && p.z >= -0.001)
return q;
if (q.x <= 0.001 && q.x >= -0.001
&& q.y <= 0.001 && q.y >= -0.001
&& q.z <= 0.001 && q.z >= -0.001)
return p;
struct Quaternions res = {
.w = p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z,
.x = p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y,
.y = p.w * q.y - p.x * q.z + p.y * q.w + p.z * q.x,
.z = p.w * q.z + p.x * q.y - p.y * q.x + p.z * q.w,
};
return res;
}
char *
chooseColor(char c)
{
switch (c) {
case FACE_FRONT:
return "\033[31;1;4m#\033[0m";
case FACE_BACK:
return "\033[32;1;4m0\033[0m";
case FACE_BOTTOM:
return "\033[33;1;4m!\033[0m";
case FACE_LEFT:
return "\033[34;1;4m%\033[0m";
case FACE_RIGHT:
return "\033[35;1;4m@\033[0m";
case FACE_TOP:
return "\033[36;1;4m-\033[0m";
case ' ':
return " ";
default:
//unused, but issues no warning
return "\033[0m";
}
}
char
chooseMainFace()
{
int total = 0;
int faces[NUM_FACES] = {0};
for (int k = 0; k < SCREEN_WIDTH * SCREEN_HEIGHT; ++k)
if (output[k] >= 0 && output[k] < NUM_FACES) {
faces[output[k]]++;
++total;
}
int max = 0, idx = 0;
for (int k = 0; k < NUM_FACES; ++k)
if (faces[k] > max) {
max = faces[k];
idx = k;
}
return max > total * 0.9 ? idx : -1;
}
void
printFrontFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCContacts:\r\n", row);
struct json_object_element_s *obj = json_value_as_object(elt->value)->start;
while (obj != NULL) {
struct json_string_s *str = json_value_as_string(obj->value);
printf("\033[%dC%s: %s\r\n", row + 4, obj->name->string, str->string);
obj = obj->next;
}
}
void
printLefttFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCSkills:\r\n", row);
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
while (arr != NULL) {
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
printf("\033[%dC%s\r\n", row + 4, json_value_as_string(obj->value)->string);
struct json_array_element_s *arr2 = json_value_as_array(obj->next->value)->start;
while (arr2 != NULL) {
struct json_string_s *str = json_value_as_string(arr2->value);
printf("\033[%dC%s\r\n", row + 8, str->string);
arr2 = arr2->next;
}
arr = arr->next;
}
}
void
printRightFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCLanguages:\r\n", row);
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
while (arr != NULL) {
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
printf("\033[%dC%s: %s / 5\r\n", row + 4, json_value_as_string(obj->value)->string, json_value_as_number(obj->next->value)->number);
arr = arr->next;
}
}
void
printBottomFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCProfessional experience:\r\n", row);
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
while (arr != NULL) {
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
struct json_string_s *position = json_value_as_string(obj->value);
struct json_object_element_s *company = json_value_as_object(obj->next->value)->start;
struct json_string_s *from = json_value_as_string(obj->next->next->value);
struct json_string_s *to = json_value_as_string(obj->next->next->next->value);
struct json_array_element_s *descr = json_value_as_array(obj->next->next->next->next->value)->start;
printf("\033[%dC%s: %s ; %s -> %s\r\n", row + 4, json_value_as_string(company->value)->string, position->string, from->string, to->string);
while (descr != NULL) {
printf("\033[%dC%s\r\n", row + 8, json_value_as_string(descr->value)->string);
descr = descr->next;
}
printf("\r\n");
arr = arr->next;
}
}
void
printBackFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCProjects:\r\n", row);
struct json_object_element_s *obj = json_value_as_object(elt->value)->start;
//ignore personnal projects
printf("\033[%dC- This cube! its made using Quaternion for 3d rotation, "
"from scratch (except the CV text), in C\r\n", row + 4);
printf("\r\n");
struct json_array_element_s *arr = json_value_as_array(obj->next->value)->start;
while (arr != NULL) {
struct json_string_s *main = json_value_as_string(json_value_as_object(arr->value)->start->value);
struct json_string_s *link = json_value_as_string(json_value_as_object(arr->value)->start->next->value);
struct json_array_element_s *descr = json_value_as_array(json_value_as_object(arr->value)->start->next->value)->start;
struct json_number_s *year = json_value_as_number(json_value_as_object(arr->value)->start->next->next->next->value);
printf("\033[%dC- %s, %s\r\n", row + 4, json_value_as_string(descr->value)->string, year->number);
while (descr != NULL) {
printf("\033[%dCo %s\r\n", row + 8, json_value_as_string(descr->value)->string);
descr = descr->next;
}
printf("\r\n");
arr = arr->next;
}
}
void
printTopFace(struct json_object_element_s *elt, int row)
{
printf("\033[%dCEducation:\r\n", row);
struct json_array_element_s *arr = json_value_as_array(elt->value)->start;
while (arr != NULL) {
struct json_object_element_s *obj = json_value_as_object(arr->value)->start;
struct json_string_s *univ_name =
json_value_as_string(json_value_as_object(obj->value)->start->value);
struct json_string_s *degree = json_value_as_string(obj->next->value);
struct json_string_s *major = json_value_as_string(obj->next->next->value);
struct json_string_s *track =
json_value_as_string(obj->next->next->next->value);
struct json_string_s *from =
json_value_as_string(obj->next->next->next->next->value);
struct json_string_s *to =
json_value_as_string(obj->next->next->next->next->next->value);
printf("\033[%dC%s: %s -> %s \r\n", row + 4, univ_name->string, from->string, to->string);
if (track == NULL || strcmp(track->string, "None") == 0)
printf("\033[%dC%s, %s\r\n", row + 8, degree->string, major->string);
else
printf("\033[%dC%s %s, %s\r\n", row + 8, degree->string, major->string, track->string);
printf("\r\n");
arr = arr->next;
}
}
void
printCV(char face, struct json_object_element_s *r, int row)
{
struct json_object_element_s start = *r;
struct json_object_element_s *next = &start;
char k = 0;
if (face >= 0)
for (; k < face; ++k)
next = next->next;
char *res;
switch (face) {
case FACE_FRONT:
printFrontFace(next, row);
break;
case FACE_LEFT:
printLefttFace(next, row);
break;
case FACE_RIGHT:
printRightFace(next, row);
break;
case FACE_BOTTOM:
printBottomFace(next, row);
break;
case FACE_BACK:
printBackFace(next, row);
break;
case FACE_TOP:
printTopFace(next, row);
break;
default:
break;
}
}
void
printAscii(struct json_value_s *json)
{
char face = chooseMainFace();
struct json_object_element_s *root = json_value_as_object(json)->start;
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
printCV(face, root, w.ws_row);
printf("\033[%d;%dH", (int)((w.ws_row - SCREEN_HEIGHT) * 1.5 / 2), (w.ws_col - SCREEN_WIDTH) / 2);
for (int k = 0; k < SCREEN_WIDTH * SCREEN_HEIGHT; ++k) {
if (k % SCREEN_WIDTH)
printf("%s", chooseColor(output[k]));
else
printf("\r\n\033[%dC", w.ws_row);
}
fflush(stdout);
}
void
rotateCube(double cubeX, double cubeY, double cubeZ, char ch)
{
struct Quaternions q = mult(Current, cubeX, cubeY, cubeZ);
double x = q.x;
double y = q.y;
double invZ = 1 / (q.z + 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);
printf("\033[2J\033[?1049l"); // escape fullscreen mode
}
void
printBanner(char keyboardInput)
{
printf("Last input character: %c, "
"Current: w:%f x:%f y%f z:%f | "
"Last: w:%f x:%f y%f z:%f | "
"Target: w:%f x:%f y%f z:%f\r\n",
keyboardInput,
Current.w, Current.x, Current.y, Current.z,
Last.w, Last.x, Last.y, Last.z,
Target.w, Target.x, Target.y, Target.z);
}
struct Quaternions
interpolate(struct Quaternions qa, struct Quaternions qb)
{
struct Quaternions res;
double cosHalfTheta =
qa.w * qb.w +
qa.x * qb.x +
qa.y * qb.y +
qa.z * qb.z;
//if qa = qb or qa = -qb then theta = 0 and we can return qa
if (fabs(cosHalfTheta) >= 1.0) {
res.w = qa.w;
res.x = qa.x;
res.y = qa.y;
res.z = qa.z;
return res;
}
if (cosHalfTheta < 0) {
qb.w = -qb.w;
qb.x = -qb.x;
qb.y = -qb.y;
qb.z = qb.z;
cosHalfTheta = -cosHalfTheta;
}
double halfTheta = acos(cosHalfTheta);
double sinHalfTheta = sqrt(1.0 - cosHalfTheta * cosHalfTheta);
//if theta = 180 degrees then result is not fully defined
// we could rotate around any axis normal to qa or qb
if (fabs(sinHalfTheta) < 0.001) {
res.w = (qa.w * 0.5 + qb.w * 0.5);
res.x = (qa.x * 0.5 + qb.x * 0.5);
res.y = (qa.y * 0.5 + qb.y * 0.5);
res.z = (qa.z * 0.5 + qb.z * 0.5);
return res;
}
interpolationStep += ACTION_STEP;
double ratioA = sin((1 - interpolationStep) * halfTheta) / sinHalfTheta;
double ratioB = sin(interpolationStep * halfTheta) / sinHalfTheta;
res.w = (qa.w * ratioA + qb.w * ratioB);
res.x = (qa.x * ratioA + qb.x * ratioB);
res.y = (qa.y * ratioA + qb.y * ratioB);
res.z = (qa.z * ratioA + qb.z * ratioB);
return res;
}
void
handleAngle(char *input)
{
if (!currentlyMoving) {
currentlyMoving = *input;
Last = Current;
switch (*input) {
case 'w':
case 'W':
Target = multQ(GET_ROTATE_X_Q(M_PI_2), Target);
break;
case 'a':
case 'A':
Target = multQ(GET_ROTATE_Y_Q(-M_PI_2), Target);
break;
case 's':
case 'S':
Target = multQ(GET_ROTATE_X_Q(-M_PI_2), Target);
break;
case 'd':
case 'D':
Target = multQ(GET_ROTATE_Y_Q(M_PI_2), Target);
break;
case 'q':
case 'Q':
Target = multQ(GET_ROTATE_Z_Q(-M_PI_2), Target);
break;
case 'e':
case 'E':
Target = multQ(GET_ROTATE_Z_Q(M_PI_2), Target);
break;
default:
currentlyMoving = 0;
//TODO idle movement
}
normalize(&Target);
} else {
if (interpolationStep < 1 - ACTION_STEP)
Current = interpolate(Last, Target);
else {
//RESET_IDLE;
Current = Target;
*input = 0;
interpolationStep = 0;
currentlyMoving = 0;
}
normalize(&Current);
}
}
char
getInput(char *keyboardInput)
{
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
char c = getchar();
if (c == '\033') {
if ((c = getchar()) == '[')
switch (c = getchar()) {
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
//
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) & ~O_NONBLOCK);
handleAngle(keyboardInput);
return c;
}
struct json_value_s *
readJson()
{
int size;
FILE *fp = fopen("./cv.json", "r");
char *json;
if (fp) {
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
json = (char *)malloc(sizeof(char) * size);
fread(json, 1, size, fp);
}
return json_parse(json, strlen(json));
}
int
main()
{
printf("\033[?1049h\033[H"); // enter fullscreen mode
char keyboardInput = 0;
signal(SIGINT, intHandler);
tcgetattr(STDIN_FILENO, &originalTerm);
struct termios t = {};
cfmakeraw(&t);
t.c_lflag |= ISIG;
t.c_cc[VINTR] = 3;
tcsetattr(STDIN_FILENO, TCSANOW, &t);
#ifdef VBUF
setvbuf(stdout, buf, _IOFBF, sizeof(buf));
#endif
Current = GET_ROTATE_Z_Q(0);
struct json_value_s *json = readJson();
while (shouldBreak) {
#ifdef VBUF
memset(buf, ' ', sizeof(buf));
#endif
memset(output, ' ', SCREEN_WIDTH * SCREEN_HEIGHT);
memset(zBuffer, DBL_MIN, 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);
rotateCube(cubeX, cubeY, CUBE_WIDTH, FACE_BACK);
rotateCube(CUBE_WIDTH, cubeX, cubeY, FACE_RIGHT);
rotateCube(-CUBE_WIDTH, cubeX, cubeY, FACE_LEFT);
rotateCube(cubeX, CUBE_WIDTH, cubeY, FACE_BOTTOM);
rotateCube(cubeX, -CUBE_WIDTH, cubeY, FACE_TOP);
}
}
printf("\033[1;1H\033[2J");
printAscii(json);
getInput(&keyboardInput);
usleep(1000000 / 60); // 60fps max
}
}