cube/main.c
2025-05-02 11:39:32 -04:00

574 lines
14 KiB
C

#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];
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 "";
}
}
char
chooseMainFace()
{
int total;
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)
{
printf("Contacts:\r\n");
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(" %s: %s\r\n", obj->name->string, str->string);
obj = obj->next;
}
}
void
printLefttFace(struct json_object_element_s *elt)
{
printf("Skills:\r\n");
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(" %s\r\n", 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(" %s\r\n", str->string);
arr2 = arr2->next;
}
arr = arr->next;
}
}
void
printRightFace(struct json_object_element_s *elt)
{
printf("Languages:\r\n");
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(" %s: %s / 5\r\n", 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)
{
printf("Professional experience:\r\n");
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(" %s: %s ; %s -> %s\r\n", json_value_as_string(company->value)->string, position->string, from->string, to->string);
while (descr != NULL) {
printf(" %s\r\n", json_value_as_string(descr->value)->string);
descr = descr->next;
}
printf("\r\n");
arr = arr->next;
}
}
void
printBackFace(struct json_object_element_s *elt)
{
printf("Projects:\r\n");
struct json_object_element_s *obj = json_value_as_object(elt->value)->start;
//ignore personnal projects
printf(" - This cube! its made using Quaternion for 3d rotation, "
"from scratch (except the CV text), in C\r\n");
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(" - %s, %s\r\n", json_value_as_string(descr->value)->string, year->number);
while (descr != NULL) {
printf(" o %s\r\n", json_value_as_string(descr->value)->string);
descr = descr->next;
}
printf("\r\n");
arr = arr->next;
}
}
void
printTopFace(struct json_object_element_s *elt)
{
printf("Education:\r\n");
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(" %s: %s -> %s \r\n", univ_name->string, from->string, to->string);
if (track == NULL || strcmp(track->string, "None") == 0)
printf(" %s, %s\r\n", degree->string, major->string);
else
printf(" %s %s, %s\r\n", degree->string, major->string, track->string);
printf("\r\n");
arr = arr->next;
}
}
void
printCV(char face, struct json_object_element_s *r)
{
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);
break;
case FACE_LEFT:
printLefttFace(next);
break;
case FACE_RIGHT:
printRightFace(next);
break;
case FACE_BOTTOM:
printBottomFace(next);
break;
case FACE_BACK:
printBackFace(next);
break;
case FACE_TOP:
printTopFace(next);
break;
default:
break;
}
}
void
printAscii(struct json_value_s *json)
{
#ifdef __APPLE__
fcntl(STDOUT_FILENO, F_SETFL, ~O_NONBLOCK);
#endif
char face = chooseMainFace();
struct json_object_element_s *root = json_value_as_object(json)->start;
printCV(face, root);
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)
{
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);
}
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)
{
char c = getchar();
if (c == '\033') {
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;
}
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()
{
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);
Current = GET_ROTATE_Z_Q(0);
struct json_value_s *json = readJson();
while (shouldBreak) {
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("\x1b[2J");
printAscii(json);
getInput(&keyboardInput);
usleep(10000);
}
}