diff --git a/.gitignore b/.gitignore index 35cb3d2..2f1b2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.exe .vscode/ -Cube.code-workspace +CMake* +build \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..459cc93 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# EquationParser + +Type an equation. See a 3D shape. + +EquationParser takes a math string like `((x^2+y^2)^0.5 - 15)^2 + z^2 < 15` and renders the implicit 3D surface it describes — no mesh, no model, just an inequality over `x`, `y`, `z` evaluated at every point in space. + +![demo](assets/demo.gif) + +--- + +## What it does + +You write an equation as a C string. The parser evaluates it at every point on a 3D grid. Wherever the condition holds, that point is part of the surface. The result gets rendered as a 3D shape. + +```c +char *str = "((x^2+y^2)^0.5 - 15)^2 + z^2 < 15"; // donut +``` + +```c +char *str = "x^4 - 5*x^2 + y^4 - 5*y^2 + z^4 - 5*z^2 + 11.8 < 0"; // tanglecube +``` + +```c +char *str = "min(min(x^2+y^2, y^2+z^2), x^2+z^2) < 1"; // three intersecting cylinders +``` + +Any implicit surface you can express as an inequality over `x`, `y`, `z` — just type it. + +--- + +## Equation syntax + +| Category | What to write | +|---|---| +| Variables | `x` `y` `z` | +| Arithmetic | `+` `-` `*` `/` `^` | +| Grouping | `(` `)` | +| Comparison | `<` `>` `=` `<=` `>=` | +| Logic | `&` `\|` | +| Functions | `abs(v)` `min(a, b)` `max(a, b)` | +| Numbers | `3` `1.5` `0.75` etc. | + +--- + +## How it works + +**1. Tokenizer** — scans the string character by character into typed tokens: numbers, variables, operators, functions. + +**2. Shunting-yard** — reorders tokens into Reverse Polish Notation, respecting operator precedence, parentheses, and unary operators. + +**3. RPN evaluator** — walks the token queue, substitutes `x/y/z` from the current point, and applies operators via a number stack. Returns a single value. + +**4. Visualization** — iterates over a 3D voxel grid, calls the evaluator at each point, and renders wherever the inequality holds. + +Parse once, evaluate millions of points. + +--- + +## API + +```c +// Parse a string into a reusable RPN queue +tokenQueue q = init("(x^2 + y^2 + z^2)^0.5 < 1"); + +// Evaluate at any (x, y, z) point — non-destructive, call repeatedly +point p = {0.5, 0.5, 0.0}; +double result = evalPolish(&q, p); + +free_queue(&q); +``` + +### Functions + +| Function | Description | +|---|---| +| `tokenQueue init(char* str)` | Parse equation string → RPN queue | +| `double evalPolish(tokenQueue* queue, point p)` | Evaluate queue at point `p` | +| `int parseRule(char* rule, token** res)` | Tokenize string, return token count | +| `void convertToPolish(...)` | Convert token array to RPN | + +--- + +## Project structure + +``` +EquationParser/ +├── include/ # Headers — parser.h, types, operator table +└── src/ # Sources — parser, stack, queue, operators +``` + +--- + +## Building + +Plain C, no external dependencies: + +```bash +gcc -o parser src/*.c -Iinclude -lm +``` diff --git a/assets/demo.gif b/assets/demo.gif new file mode 100644 index 0000000..350de6d Binary files /dev/null and b/assets/demo.gif differ diff --git a/eval.h b/include/eval.h similarity index 85% rename from eval.h rename to include/eval.h index 8792431..f79ca12 100644 --- a/eval.h +++ b/include/eval.h @@ -1,15 +1,18 @@ #ifndef _EVAL #define _EVAL -#include -#include -#include -#include #include -#include "stack.h" - +#include +#include +#include -typedef enum {LEFT, RIGHT, NONE} ASSOC; +#include "stack.h" +typedef enum +{ + LEFT, + RIGHT, + NONE +} ASSOC; typedef struct op { @@ -18,7 +21,7 @@ typedef struct op ASSOC assoc; char isUnary; double (*evalfunc)(double, double); -}operator; +} operator; double evalplus(double a, double b); double evalminus(double a, double b); @@ -36,10 +39,6 @@ double evalmin(double a, double b); double evaland(double a, double b); double evalor(double a, double b); - - - - operator* getop(char c); #endif \ No newline at end of file diff --git a/parser.h b/include/parser.h similarity index 65% rename from parser.h rename to include/parser.h index 41f0887..59c797a 100644 --- a/parser.h +++ b/include/parser.h @@ -7,8 +7,19 @@ #include #include #include -#include #include + +// Cross-platform sleep and clear screen +#ifdef _WIN32 + #include + #define SLEEP(ms) Sleep(ms) + #define CLEAR_SCREEN() system("cls") +#else + #include + #define SLEEP(ms) usleep((ms) * 1000) + #define CLEAR_SCREEN() system("clear") +#endif + #include "stack.h" #include "eval.h" @@ -25,5 +36,6 @@ int parseRule(char* rule, token** tokens); void printTokens(token* tokens, int size); void convertToPolish(tokenQueue* queue, token* tokens, int size); double evalPolish(tokenQueue* queue, point p); +tokenQueue init(char* str); #endif \ No newline at end of file diff --git a/include/renderer.h b/include/renderer.h new file mode 100644 index 0000000..ce82302 --- /dev/null +++ b/include/renderer.h @@ -0,0 +1,27 @@ +#ifndef RENDER +#define RENDER + +#include +#include +#include +#include + +typedef struct rgb +{ + int r; + int g; + int b; +} RGB; + +typedef struct angles +{ + float sinA, sinB, sinC; + float cosA, cosB, cosC; +} Angles; + +void calculatePoint(int i, int j, int k, int width, int height, float *zbuffer, char *buffer, RGB *colorbuffer, Angles* angle); +char map(float a); +RGB ColorMap(float a); +void renderObject(int width, int height, char *buffer, RGB *colorbuffer, char *PrevBuffer); + +#endif \ No newline at end of file diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..3752e23 --- /dev/null +++ b/include/stack.h @@ -0,0 +1,65 @@ +#ifndef _STACK_H +#define _STACK_H +#include +#include +#include + +#include "eval.h" + +typedef enum +{ + T_NUMBER, + T_OPERATOR, + T_VARIABLE, +} T_TYPE; + +typedef struct token +{ + union value + { + double num; // T_NUMBER + operator* op; // T_OPERATOR + char var; // T_VARIABLE + } value; + T_TYPE type; + +} token; + +typedef struct tokenNode +{ + token _token; + struct tokenNode* next; +} tokenNode; + +typedef enum +{ + NOT_OK, + OK +} Tboolean; + +typedef struct +{ + tokenNode* tokenTop; + int top; +} tokenStack; + +typedef struct tokenQueue +{ + tokenNode *head, *tail; + int size; +} tokenQueue; + +void initialize_queue(tokenQueue*); +void enqueue(tokenQueue*, token token); +Tboolean dequeue(tokenQueue* queue, token* token); +void print_queue(tokenQueue* queue); +void free_queue(tokenQueue* queue); +int convert_to_array(token* tokens, tokenQueue* queue); + +void initialize_stack(tokenStack* Pstack); +void push(tokenStack* Pstack, token item); +Tboolean pop(tokenStack* Pstack, token* Pitem); +Tboolean peek(tokenStack* PStack, token* token); +void free_stack(tokenStack* Pstack); + +#endif \ No newline at end of file diff --git a/eval.c b/src/eval.c similarity index 100% rename from eval.c rename to src/eval.c diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..c63e33a --- /dev/null +++ b/src/main.c @@ -0,0 +1,90 @@ +#include "parser.h" +#include "renderer.h" + +#define WIDTH 140 +#define HEIGHT 45 +#define SIZE (WIDTH * HEIGHT) + +// Size of object +const float cWidth = 40; + +// EQUATION +char* str = "x^4 - 5*x^2 + y^4 - 5*y^2 + z^4 - 5*z^2 + 11.8 < 0"; + +// Buffers for output points +char buffer[SIZE]; +// Coordinates of projection z-buffering +float zbuffer[SIZE]; +// Buffer for color +RGB colorbuffer[SIZE]; +// Prev buffer +char PrevBuffer[SIZE]; + +float A, B, C; // Angles +int background = ' '; +float deltaA, deltaB, deltaC; // delta of Angle + +int main() +{ + float i, j, g; + + CLEAR_SCREEN(); + + // delta of Angles for rotation + deltaA = 0.09; + deltaB = 0.05; + deltaC = -0.05; + + tokenQueue queue = init(str); + + // Start + printf("The program will start in 5sec, open console on FULL SCREEN"); + SLEEP(5000); + + while (1) + { + memset(buffer, background, sizeof(buffer)); + memset(zbuffer, 0, sizeof(zbuffer)); + + Angles ang = {sin(A), sin(B), sin(C), cos(A), cos(B), cos(C)}; + + for (i = -cWidth / 2; i < cWidth / 2; i += 1) + { + for (j = -cWidth / 2; j < cWidth / 2; j += 1) + { + for (g = -cWidth / 2; g <= cWidth / 2; g += 1) + { + point p = {i, j, g}; + if (evalPolish(&queue, p)) + { + calculatePoint(i, j, g, WIDTH, HEIGHT, zbuffer, buffer, + colorbuffer, &ang); + } + } + } + } + + // Move cursor to home position + printf("%c[%d;%df", 0x1B, 0, 0); + + // Coordinates of X Y Z (Debug) + printf("%0.2f %0.2f %0.2f\n", A, B, C); + + // Print all points + renderObject(WIDTH, HEIGHT, buffer, colorbuffer, PrevBuffer); + + // Change angles + if (abs(C) >= 100) + { + deltaC *= -1; + } + if (abs(B) >= 100) deltaB *= -1; + if (abs(A) >= 100) deltaA *= -1; + + C += deltaC; // X rotation (roll) + B += deltaB; // Y rotation (pitch) + A += deltaA; // Z rotation (yaw) + + SLEEP(10); + } +} diff --git a/parser.c b/src/parser.c similarity index 96% rename from parser.c rename to src/parser.c index 8a682ba..ba4b74d 100644 --- a/parser.c +++ b/src/parser.c @@ -1,28 +1,5 @@ #include "parser.h" -int main() -{ - char* str = "x^2 + y^2 <= z^2 & z <= 10 & 0 <= z"; - token* tokens; - int size = parseRule(str, &tokens); - printTokens(tokens, size); - tokenQueue queue; - initialize_queue(&queue); - convertToPolish(&queue, tokens, size); - printf("\n\n"); - print_queue(&queue); - - printf("\n\n"); - free(tokens); - - point p = {1 , 2 , 11}; - double res = evalPolish(&queue, p); - printf("%g\n", res); - - //Sleep(100000); - return 0; -} - void printTokens(token* tokens, int size) { int i = 0; @@ -263,4 +240,17 @@ double evalPolish(tokenQueue* queue, point p) free_stack(&numstack); return res; -} \ No newline at end of file +} + +// Initialize parser +tokenQueue init(char* str) +{ + token *tokens; + int size = parseRule(str, &tokens); + tokenQueue queue; + initialize_queue(&queue); + convertToPolish(&queue, tokens, size); + + free(tokens); + return queue; +} diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..dc6d95f --- /dev/null +++ b/src/renderer.c @@ -0,0 +1,121 @@ +#include "renderer.h" +#define N + +// Calculate position of the point +void calculatePoint(int i, int j, int k, int width, int height, float *zbuffer, char *buffer, RGB *colorbuffer, Angles* angle) +{ + float x, y, z; // Point coords + int xproj, yproj; // projection position + int cam = 70; // distance from camera to object + float zdist = 40; // distance from camera to screen + int screenpos; // position of a point on the screen + float ooz; // z-buffer 1/z + + // // Sines and Cosines + // float sinA = sin(A), cosA = cos(A); // X-axis rotation + // float sinB = sin(B), cosB = cos(B); // Y-axis rotation + // float sinC = sin(C), cosC = cos(C); // Z-axis rotation + + // Rotate around Z + float x1 = i; + float y1 = j * angle->cosA - k * angle->sinA; + float z1 = j * angle->sinA + k * angle->cosA; + + // Rotate around Y + float x2 = x1 * angle->cosB + z1 * angle->sinB; + float y2 = y1; + float z2 = -x1 * angle->sinB + z1 * angle->cosB; + + // Rotate around X + float x3 = x2 * angle->cosC - y2 * angle->sinC; + float y3 = x2 * angle->sinC + y2 * angle->cosC; + float z3 = z2; + + x = x3; + y = y3; + z = z3 + cam; + ooz = 1 / z; + + xproj = (int)(width / 2 + x * zdist * ooz * 2); + yproj = (int)(height / 2 + y * zdist * ooz); + screenpos = xproj + yproj * width; + + if (screenpos >= 0 && screenpos < width * height) + { + if (ooz > zbuffer[screenpos]) + { + zbuffer[screenpos] = ooz; + buffer[screenpos] = map(ooz); + colorbuffer[screenpos] = ColorMap(ooz); + } + } +} + +// Will change symbols depending on distance +char map(float a) +{ + return (char)(a * 1000 + 66); +} + +// Will shade colors on object +RGB ColorMap(float a) +{ + + float shade = a * 10000; + int r = 0, g = 0, b = 0, mod; + + mod = (int)shade %10; + // Color swap + r = ((int)shade + mod*10); + g = b = r; + if (r > 255) + { + r = 255; + g = 255; + b = 255; + } + else if (r < 0) + { + r = 0; + g = 0; + b = 0; + } + + return (RGB){r, g, b}; +} + +// Will print one frame of object +void renderObject(int width, int height, char *buffer, RGB *colorbuffer, char* PrevBuffer) +{ + int k; + int lastcolor, newcolor; + int row; + int col; + + for (k = 0; k < width * height; k++) + { + if (buffer[k] != PrevBuffer[k]) + { + // Save symbols + PrevBuffer[k] = buffer[k]; + + // Position check for jump + row = k / width; + col = k % width; + // Jump to specific point on the screen + printf("\033[%d;%dH", row + 1, col + 1); + + // Color swap + newcolor = (buffer[k] != ' ') ? 1 : 0; + if (newcolor != lastcolor) + { + lastcolor = newcolor; + printf("\033[38;2;%d;%d;%dm", colorbuffer[k].r, colorbuffer[k].g, colorbuffer[k].b); + } + + // Print Char + putchar(buffer[k]); + } + } + fflush(stdout); +} diff --git a/stack.c b/src/stack.c similarity index 100% rename from stack.c rename to src/stack.c diff --git a/stack.h b/stack.h deleted file mode 100644 index 293f831..0000000 --- a/stack.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef _STACK_H -#define _STACK_H -#include -#include -#include -#include -#include "eval.h" - - - - -typedef enum { - T_NUMBER, - T_OPERATOR, - T_VARIABLE, -} T_TYPE; - -typedef struct token -{ - union value - { - double num; //T_NUMBER - operator* op; //T_OPERATOR - char var; //T_VARIABLE - }value; - T_TYPE type; - -}token; - -typedef struct tokenNode -{ - token _token; - struct tokenNode* next; -}tokenNode; - -typedef enum {NOT_OK, OK} Tboolean; - -typedef struct -{ - tokenNode *tokenTop; - int top; -}tokenStack; - -typedef struct tokenQueue -{ - tokenNode *head, *tail; - int size; -}tokenQueue; - - -void initialize_queue(tokenQueue*); -void enqueue(tokenQueue*,token token); -Tboolean dequeue(tokenQueue* queue,token *token); -void print_queue(tokenQueue* queue); -void free_queue(tokenQueue* queue); -int convert_to_array(token* tokens,tokenQueue* queue); - -void initialize_stack (tokenStack *Pstack); -void push (tokenStack *Pstack, token item); -Tboolean pop (tokenStack *Pstack, token *Pitem); -Tboolean peek(tokenStack *PStack, token* token); -void free_stack (tokenStack *Pstack); - - -#endif \ No newline at end of file