mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
330 lines
11 KiB
Markdown
330 lines
11 KiB
Markdown
<div align="center">
|
||
<h1>Grapher</h1>
|
||
<p><em>An interactive mathematical expression plotter for BoredOS, supporting both 2D and 3D visualizations.</em></p>
|
||
</div>
|
||
|
||
---
|
||
|
||
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 = <expression>` when no `y` or `z` is present, or as `<expression> = 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.
|