Grapher

An interactive mathematical expression plotter for BoredOS, supporting both 2D and 3D visualizations.

--- Grapher is a built-in GUI application that lets you type any mathematical equation and see it plotted in real time. It supports 2D explicit and implicit curves as well as full 3D surface visualization — including both explicit surfaces (`z = f(x, y)`) and implicit surfaces (`f(x, y, z) = c`). > [!NOTE] > Grapher is located at `src/userland/gui/grapher.c`. It runs as a standard BoredOS GUI process and can be launched from the terminal or from the dock. --- ## Features at a Glance | Feature | Details | |---|---| | **2D Explicit** | Plot `y = f(x)` curves | | **2D Implicit** | Plot any `f(x, y) = g(x, y)` contour via marching squares | | **3D Explicit** | Plot `z = f(x, y)` surfaces | | **3D Implicit** | Plot `f(x, y, z) = c` surfaces via Z-axis root finding (has rendering issues in filled mode)| | **Rendering modes** | Wireframe and filled polygon modes | | **Height coloring** | Surfaces are colored by a blue→green→yellow→red gradient based on Z height | | **Phong-style shading** | Filled mode computes per-face normals and applies diffuse + ambient lighting | | **Parallel rendering** | Evaluation and projection are distributed across 4 worker threads via `sys_parallel_run` | | **Preset equations** | 7 built-in presets accessible from the toolbar | | **Auto-fit** | 2D view auto-fits the Y axis to the plotted curve on first plot | | **Z-buffer** | All 3D drawing uses a per-pixel depth buffer with atomic CAS for thread safety | --- ## Launching Grapher From the BoredOS terminal: ```sh grapher ``` Or click the **Grapher icon** in the system dock. --- ### Toolbar Controls | Control | Function | |---|---| | **Equation box** | Type your mathematical expression, then press **Enter** or **Plot** | | **Plot button** | Parse and render the current equation | | **Wire / Filled button** | Toggle wireframe vs. shaded polygon mode (3D only) | | **Presets button** | Open a dropdown of example equations | ### Status Bar Controls (3D mode) | Control | Function | |---|---| | **`+` button** | Increase the 3D world range (zoom out in world space) | | **`-` button** | Decrease the 3D world range (zoom in in world space) | --- ## Keyboard Shortcuts | Shortcut | Action | |---|---| | **Enter** (in equation box) | Plot the equation | | **Ctrl + R** | Reset the view to defaults | | **F** | Toggle filled / wireframe rendering (3D mode) | | **Scroll wheel** | Zoom in/out (2D mode adjusts viewport; 3D mode adjusts camera zoom) | | **Right-click drag** | Rotate the 3D surface | --- ## Writing Equations Grapher parses equations entered as plain text. It supports a subset of mathematical notation with automatic implicit multiplication. ### Supported Functions | Syntax | Meaning | |---|---| | `sin(x)` | Sine | | `cos(x)` | Cosine | | `tan(x)` | Tangent | | `sqrt(x)` | Square root | | `abs(x)` | Absolute value | | `log(x)` | Natural logarithm (base *e*) | ### Supported Operators | Operator | Meaning | |---|---| | `+` `-` `*` `/` | Arithmetic | | `^` | Exponentiation (right-associative) | | `(` `)` | Grouping | ### Special Values | Token | Value | |---|---| | `pi` or `PI` | π ≈ 3.14159… | ### Implicit Multiplication Adjacent tokens that would normally require a `*` are multiplied automatically: ``` 2x → 2 * x 3sin(x) → 3 * sin(x) (x+1)(x) → (x+1) * x ``` ### How Equations Are Classified Grapher looks at which variables appear in your equation to automatically choose the rendering mode: | Equation form | Auto-detected as | |---|---| | `y = f(x)` or just `f(x)` | 2D explicit | | `f(x, y) = g(x, y)` | 2D implicit | | `z = f(x, y)` | 3D explicit | | `f(x, y, z) = c` | 3D implicit | If you omit the `=` sign, Grapher treats the input as `y = ` when no `y` or `z` is present, or as ` = 0` otherwise. --- ## Example Equations ### 2D Examples ``` y = sin(x) y = x^2 y = cos(x)*x y = abs(x) - 2 x^2 + y^2 = 25 ← circle (implicit) y = log(x) ``` ### 3D Explicit Examples ``` z = sin(x)*cos(y) z = x^2 - y^2 ← saddle surface z = sqrt(25 - x^2 - y^2) ``` ### 3D Implicit Examples ``` x^2 + y^2 + z^2 = 25 ← sphere x^2 + y^2 = 16 ← cylinder x^2 + y^2 - z^2 = 1 ← hyperboloid ``` --- ## Navigation Controls ### 2D Mode | Input | Action | |---|---| | **Scroll up** | Zoom in | | **Scroll down** | Zoom out | | **Ctrl+R** | Reset to default view (`x: [-10, 10]`) | ### 3D Mode | Input | Action | |---|---| | **Right-click drag** | Rotate the surface (orbit camera) | | **Scroll up** | Zoom camera in | | **Scroll down** | Zoom camera out | | **`+` / `-` buttons** | Increase / decrease world range | | **Ctrl+R** | Reset rotation and zoom | > [!TIP] > In 3D mode, the surface auto-rotates slowly by default. This can be disabled by setting `#define ROTATE 0` in the source file. --- ## Architecture Overview Grapher is implemented as a single self-contained C file. Below is a high-level breakdown of its major components: ### Math Library Grapher uses the BoredOS freestanding **`libc/math.h`** library, which provides all the math functions it needs without depending on a host standard library: | Function | Description | |---|---| | `sin`, `cos`, `tan` | Trigonometry via Taylor series (8 terms, range-reduced to `[-π, π]`) | | `sqrt` | Newton-Raphson iteration (25 steps) | | `log` | Natural logarithm via Padé-style series | | `log2`, `log10` | Derived from `log` | | `exp` | Range-reduced Taylor series for `e^x` | | `pow` | Integer exponents use fast binary exponentiation; fractional exponents use `exp(e * log(b))` | | `fabs`, `fmod` | Absolute value and floating-point remainder | | `floor`, `ceil` | Rounding | | `sinh`, `cosh`, `tanh` | Hyperbolic functions | | `hypot`, `fmin`, `fmax`, `fclamp` | Utility helpers | The constants `M_PI`, `M_E`, `M_LN2`, `M_SQRT2` are also defined in the header. This library is automatically linked into every userland ELF — any app can `#include "math.h"` to use it. ### Expression Parser Equations are parsed in three stages: 1. **Tokenizer** (`tokenize`) — converts the input string into a flat token array. Handles implicit multiplication by inserting `*` tokens where needed. 2. **Recursive Descent Parser** (`parse_expr`, `parse_term`, `parse_power`, `parse_unary`, `parse_atom`) — produces an Abstract Syntax Tree (AST) with up to `MAX_NODES = 128` nodes. 3. **Bytecode Compiler** (`compile_ast`) — walks the AST in post-order and emits a flat instruction sequence for a simple stack machine. This avoids recursive evaluation during rendering hot paths. The resulting bytecode is then executed by `run_bc` for every sample point. ### Rendering Pipeline #### 2D Rendering - **Explicit** — evaluates `y = f(x)` at every pixel column and connects adjacent samples with Bresenham lines. - **Implicit** — applies **marching squares** on a 200×130 grid to find sign changes in `f(x,y) - g(x,y)` and plots intersection pixels. #### 3D Rendering The 3D pipeline is a three-pass system, each parallelised across 4 threads: | Pass | Function | Description | |---|---|---| | 1 | **Evaluation** | Samples the surface at every grid point in a `GRID_3D × GRID_3D` grid | | 2 | **Projection** | Projects 3D world coordinates to 2D screen coordinates with perspective | | 3 | **Drawing** | Rasterizes wireframe lines or filled triangles with Z-buffering | For **implicit surfaces**, Pass 1 additionally marches along the Z axis at 512 steps per column using bisection (15 iterations) to find up to `MAX_Z_PER_POINT = 4` roots. Surface normals are estimated using central finite differences of the implicit function. #### Filled Mod When filled mode is active, each quad cell is split into two triangles. The average surface normal across the four corner vertices is computed and fed into `apply_shading`, which calculates: ``` intensity = ambient(0.3) + diffuse(0.7) * dot(normal, light_direction) ``` The light direction is fixed at `(0.577, 0.707, 0.408)` (normalized diagonal). #### Z-Buffer The depth buffer (`graph_zb`) stores integer depth values. `gfb_pixel_z` uses a **compare-and-swap (CAS) loop** via `__atomic_compare_exchange_n` so multiple parallel draw threads cannot produce race conditions. ### Coordinate Systems #### 2D World coordinates map linearly to screen pixels: ```c screen_x = (wx - view_x_min) / (view_x_max - view_x_min) * graph_w screen_y = (view_y_max - wy) / (view_y_max - view_y_min) * graph_h ``` #### 3D Points are first rotated by two Euler angles (`rot_y`, `rot_x`) then projected with a simple perspective divide: ``` persp = d / (pz + d) // d = range_3d * 5 sx = px * scale * persp + screen_cx sy = -py * scale * persp + screen_cy ``` --- ## Configuration Constants These can be changed at the top of `grapher.c` to tune behaviour: | Constant | Default | Effect | |---|---|---| | `ROTATE` | `1` | Set to `0` to disable auto-rotation in 3D mode | | `GRID_3D` | `256` | Grid resolution for 3D sampling. Higher = more detail, much slower | > [!WARNING] > Setting `GRID_3D` too high (e.g. 9000) will exhaust available memory. The `surf` grid and `surf_x`/`surf_y_3d` arrays are statically allocated at compile time: memory usage grows as **O(GRID_3D²)**. Values above ~512 are not recommended. > [!TIP] > `GRID_3D = 256` gives a good balance of detail and performance on typical BoredOS hardware emulation. --- ## Color Palette 3D surfaces are colored by height using a 4-stop rainbow ramp: ``` Low → Blue → Cyan → Green → Yellow → Red → High ``` --- ## Preset Equations The built-in presets are shown in the dropdown when you click **Presets**: | Label | Type | |---|---| | `y = sin(x)` | 2D explicit | | `y = x^2` | 2D explicit | | `y = cos(x)*x` | 2D explicit | | `z = sin(x)*cos(y)` | 3D explicit | | `z = x^2 - y^2` | 3D explicit | | `x^2+y^2+z^2=25` | 3D implicit (sphere) | | `x^2+y^2=16` | 3D implicit (cylinder) | --- ## Known Limitations - **No parameter slider** — equations are static; there is no way to animate a parameter. - **No multiple equations** — only one equation can be graphed at a time. - **Implicit surface gaps** — very thin or high-frequency implicit surfaces may have small gaps due to the fixed Z marching step count (512 steps per column). - **3D implicit performance** — implicit surfaces require significantly more work than explicit ones; evaluating `x^2+y^2+z^2=25` at `GRID_3D=256` takes a noticeable fraction of a second. - **Integer axis labels only for large values** — very large axis values are capped at `>2G` or `<-2G` due to `itoa` limitations. - **3D polygon implicit mode** - 3D polygon implicit mode has problems with connecting hemispheres of a circle causing for dead space on the equator.