498 lines
14 KiB
Markdown
498 lines
14 KiB
Markdown
# Architecture Documentation
|
||
|
||
This document explains the architecture of the EID Wireless documentation system and its interactive diorama renderer.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
The EID Wireless documentation is a **text-first** technical resource that uses interactive 3D visualizations (dioramas) as *supplemental* learning tools. The core principle is: **readers should be able to understand the concepts without interactives, but gain deeper insight through them**.
|
||
|
||
This is achieved through a hybrid architecture:
|
||
|
||
1. **Typst** generates static HTML documents from `.typ` source files
|
||
2. **Custom JavaScript renderer** provides interactive dioramas embedded in those pages
|
||
3. **Vite** bundles everything together for development and production
|
||
|
||
The dioramas are not the primary content—they're visual aids that appear *after* conceptual explanations and serve to reinforce understanding through visualization.
|
||
|
||
---
|
||
|
||
## Architecture Goals
|
||
|
||
### Primary Goal: Text-First Documentation
|
||
|
||
The documentation system prioritizes readable, searchable, accessible text content. Interactive dioramas:
|
||
|
||
- Are **optional** and **degrade gracefully** (pages work without JavaScript)
|
||
- Are **out-of-the-way** (don't interfere with reading flow)
|
||
- Are **supplemental** (don't contain core information that isn't in text)
|
||
- Are **mobile-friendly** (scrolling behavior is preserved by default)
|
||
|
||
### Secondary Goals
|
||
|
||
1. **Accurate Minecraft rendering** - Dioramas must faithfully represent block states, rotations, and redstone behavior
|
||
2. **Efficient rendering** - Multiple dioramas can appear on a single page; rendering must be performant
|
||
3. **Easy authoring** - Content creators should focus on concepts, not graphics programming
|
||
4. **Version independence** - Documentation should work across different Minecraft versions
|
||
|
||
---
|
||
|
||
## System Architecture
|
||
|
||
### High-Level Flow
|
||
|
||
```
|
||
Typst Source (.typ files) ──▶ HTML + JavaScript ──▶ Browser
|
||
│
|
||
└─▶ Interactive Dioramas (WebGL)
|
||
```
|
||
|
||
### Components
|
||
|
||
#### 1. Typst Document Generator
|
||
|
||
**Role**: Converts `.typ` source files into HTML pages
|
||
|
||
**Key Files**:
|
||
- `main.typ` - Main entry point, defines document structure
|
||
- `lib.typ` - Library functions (`diorama()`, `callout()`, etc.)
|
||
- `content/*.typ` - Documentation chapters
|
||
- `typst.toml` - Package metadata
|
||
|
||
**Responsibilities**:
|
||
- Compile `.typ` files into HTML using `tinymist` language server
|
||
- Embed interactive elements via `#show` directives
|
||
- Generate navigation between pages
|
||
|
||
**Key Pattern** - Diorama Embedding:
|
||
|
||
```typ
|
||
#show raw.where(lang: "mc-diorama"): it => diorama(
|
||
orbit: true,
|
||
pan: true,
|
||
autoplay: true,
|
||
it.text,
|
||
)
|
||
```
|
||
|
||
This directive tells Typst to:
|
||
1. Find all `mc-diorama` code blocks
|
||
2. Wrap them in the `diorama()` function
|
||
3. Pass block content as the world definition
|
||
|
||
#### 2. World Definition System
|
||
|
||
**Role**: Defines Minecraft scenes using a timeline-based domain-specific language
|
||
|
||
**Format**:
|
||
```
|
||
p x y z block_type properties
|
||
t time
|
||
```
|
||
|
||
Where:
|
||
- `p` places or modifies blocks at coordinates
|
||
- `t` advances the timeline
|
||
- Properties are `key=value` pairs (e.g., `facing=east`, `powered=true`)
|
||
|
||
**Example**:
|
||
```
|
||
p 0 0 0 dropper facing=up triggered=false
|
||
p 0 1 0 dropper facing=up triggered=false
|
||
t 2
|
||
p 0 0 0 triggered=true
|
||
t 4
|
||
p 0 0 0 triggered=false
|
||
```
|
||
|
||
This format is designed to be:
|
||
- **Human-readable** - Easy to write and debug
|
||
- **Concise** - Minimal boilerplate
|
||
- **Timeline-aware** - Changes occur at specific game ticks
|
||
|
||
#### 3. WebGL Renderer (Engine)
|
||
|
||
**Role**: Renders Minecraft blocks as 3D models using WebGL
|
||
|
||
**Key Files**:
|
||
- `src/engine.js` - Main renderer class
|
||
- `src/world.js` - World state management
|
||
- `src/diorama.js` - Camera and view management
|
||
- `src/math.js` - Matrix operations (orthographic projection, look-at, etc.)
|
||
|
||
**Architecture**:
|
||
|
||
```
|
||
Engine (singleton)
|
||
├── Dioramas (multiple per page)
|
||
│ ├── Canvas (2D context for offscreen rendering)
|
||
│ └── Frame (world timeline state)
|
||
└── Resources (blockstates, models, textures)
|
||
├── Cache (async resource loading)
|
||
├── Blockstate Handler (parses JSON blockstate definitions)
|
||
├── Model Handler (builds geometry from models)
|
||
└── Texture Handler (atlas textures into single canvas)
|
||
```
|
||
|
||
**Key Features**:
|
||
- **Instanced rendering** - Thousands of blocks rendered efficiently
|
||
- **Lazy updates** - Only re-renders when content changes
|
||
- **View culling** - Skips rendering for off-screen dioramas
|
||
- **Texture atlasing** - Packs all textures into single texture for batch rendering
|
||
|
||
#### 4. Controller System
|
||
|
||
**Role**: Handles user interaction with dioramas
|
||
|
||
**Key Files**:
|
||
- `src/controller.js` - SpacetimeController class
|
||
- `src/components.js` - Custom HTML elements
|
||
|
||
**Features**:
|
||
- **Orbit controls** - Rotate view (left drag)
|
||
- **Pan controls** - Move view (middle drag or shift+left)
|
||
- **Zoom controls** - Scale view (right drag or ctrl+left)
|
||
- **Timeline autoplay** - Auto-play animations with configurable speed
|
||
- **Mobile support** - Touch gestures with scroll delegation
|
||
- **State preservation** - Camera position and timeline state can be reset
|
||
|
||
**Mobile-Specific Behavior**:
|
||
- Default browser scroll is preserved (diagram doesn't capture scroll)
|
||
- Touch interaction requires deliberate engagement (200ms timeout)
|
||
- 1-finger touch: orbit (don't interfere with scroll)
|
||
- 2-finger touch: pan + zoom (pinch gesture)
|
||
|
||
#### 5. Build System
|
||
|
||
**Role**: Integrates Typst output with JavaScript rendering
|
||
|
||
**Key Files**:
|
||
- `vite.config.js` - Vite configuration
|
||
- `mise.toml` - Development tool configuration
|
||
- `dist/` - Production output directory
|
||
|
||
**Workflow**:
|
||
|
||
```bash
|
||
# Development
|
||
mise run dev # Runs Vite dev server + Typst watch
|
||
|
||
# Build
|
||
mise run build # Compile Typst + bundle with Vite
|
||
```
|
||
|
||
**Vite Configuration**:
|
||
- `root: .typ-bundle` - Typst-generated HTML is the Vite root
|
||
- `publicDir: public` - Static assets served from Minecraft assets
|
||
- Alias `/src` → `src/` - JavaScript modules accessible from HTML
|
||
- Watch mode monitors both `.typ` and `src/**/*` files
|
||
|
||
---
|
||
|
||
## Data Flow
|
||
|
||
### 1. Document Compilation
|
||
|
||
```
|
||
Typst Source (.typ)
|
||
↓ (tinymist)
|
||
HTML + Scripts
|
||
↓ (Vite bundling)
|
||
dist/index.html
|
||
```
|
||
|
||
### 2. World Parsing
|
||
|
||
```
|
||
mc-diorama Code Block
|
||
↓ (World constructor)
|
||
World Object
|
||
├── events[] - Timeline events (changes at each tick)
|
||
├── initialState - Block state at t=0
|
||
└── labels - Named timeline positions
|
||
```
|
||
|
||
### 3. Frame Playback
|
||
|
||
```
|
||
World Object
|
||
↓ (WorldFrame)
|
||
Frame Object
|
||
├── blocks[] - Current block state
|
||
├── currentTime - Timeline position
|
||
└── listeners[] - Callbacks on state change
|
||
```
|
||
|
||
### 4. Rendering Pipeline
|
||
|
||
```
|
||
Frame State Change
|
||
↓
|
||
Engine.updateAll()
|
||
├── Parse blocks → Blockstates → Models
|
||
├── Build geometry (positions, normals, UVs)
|
||
├── Update texture atlas
|
||
└── Create instanced meshes (VBOs)
|
||
|
||
Frame State Change (again)
|
||
↓
|
||
Engine.render()
|
||
├── Check visibility (cull off-screen dioramas)
|
||
├── Update camera matrices (orthographic projection)
|
||
├── Draw instanced meshes (WebGL)
|
||
└── Copy to 2D canvas (for HTML display)
|
||
```
|
||
|
||
### 5. Resource Loading
|
||
|
||
```
|
||
Request Blockstate/Model/Texture
|
||
↓
|
||
Cache.getAsync()
|
||
├── Check if already loading → return promise
|
||
├── Check if already loaded → return data
|
||
└── Kick off fetch + prepare fallback
|
||
|
||
Fetch Complete
|
||
↓
|
||
Handler.process()
|
||
├── Parse JSON (blockstate/model)
|
||
├── Build geometry (model)
|
||
└── Return structured data
|
||
↓
|
||
Cache.onResolve() → Engine.requestUpdate()
|
||
```
|
||
|
||
---
|
||
|
||
## User Experience: What Users Actually See
|
||
|
||
Dioramas appear as interactive 3D diagrams within the documentation:
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ Text explanation of redstone... │
|
||
│ (this is the primary content) │
|
||
├─────────────────────────────────────┤
|
||
│ ┌───────────────────────────────┐ │
|
||
│ │ [Diorama: 3D interactive │ │
|
||
│ │ view of the mechanism] │ │
|
||
│ │ │ │
|
||
│ │ (click/drag to interact) │ │
|
||
│ └───────────────────────────────┘ │
|
||
│ Caption: "Figure 1: Mechanism" │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
**Key UX Principles**:
|
||
|
||
1. **Dioramas are after the text** - Readers understand conceptually first, then visualize
|
||
2. **Dioramas don't steal scroll** - Default browser scroll works; interaction requires deliberate action
|
||
3. **Mobile-first interaction** - Touch gestures don't interfere with page navigation
|
||
4. **Progressive enhancement** - Pages work without JS; dioramas add value when JS is available
|
||
|
||
### Camera System
|
||
|
||
Dioramas use **orthographic projection** (not perspective) because:
|
||
|
||
1. Redstone mechanisms are small and detailed
|
||
2. Orthographic preserves proportions
|
||
3. Better for technical diagrams
|
||
4. Easier to measure distances
|
||
|
||
**Camera Parameters**:
|
||
- `target` - Point camera looks at (world coordinates)
|
||
- `radius` - Distance from target
|
||
- `theta` - Horizontal rotation angle (degrees)
|
||
- `phi` - Vertical rotation angle (degrees)
|
||
|
||
**Matrix Calculation**:
|
||
1. Convert spherical coordinates to Cartesian (eye position)
|
||
2. Calculate view direction and up vector
|
||
3. Build view matrix via `mat4LookAt()`
|
||
4. Build orthographic projection matrix based on radius and aspect ratio
|
||
5. Combine: `viewProj = projection × view`
|
||
|
||
### Instanced Rendering
|
||
|
||
Each unique block variant gets its own instanced mesh:
|
||
|
||
```
|
||
Geometry (positions, normals, UVs, tints, shades)
|
||
↓
|
||
Instance Data
|
||
├── Matrix[] (16 floats each) - World transformation
|
||
└── Color[] (3 floats each) - Redstone power tint
|
||
↓
|
||
GPU Buffer (VBO)
|
||
↓
|
||
DrawElementsInstanced()
|
||
```
|
||
|
||
This allows thousands of blocks to be rendered in a single draw call.
|
||
|
||
### Texture Atlasing
|
||
|
||
All block textures are packed into a single canvas:
|
||
|
||
```
|
||
16×16px blocks
|
||
↓
|
||
Atlas Canvas (256×256px default)
|
||
├── Cell 0: block_0.png
|
||
├── Cell 1: block_1.png
|
||
├── Cell 2: block_2.png
|
||
└── ...
|
||
↓
|
||
UV Map
|
||
├── u, v (cell position)
|
||
└── du, dv (cell size)
|
||
```
|
||
|
||
Texture loading is **asynchronous** but **synchronous fallback** is provided:
|
||
- Renderer immediately gets UV coordinates
|
||
- Texture data loads in background
|
||
- Fallback texture (black silhouette) shown until loaded
|
||
|
||
---
|
||
|
||
## Interactive Element Design
|
||
|
||
### Accessibility First
|
||
|
||
Dioramas must work without JavaScript:
|
||
|
||
```html
|
||
<!-- Without JS: plain text world definition -->
|
||
<mc-diorama>
|
||
<script type="text/mc-world">
|
||
p 0 0 0 dropper...
|
||
</script>
|
||
</mc-diorama>
|
||
|
||
<!-- With JS: interactive canvas -->
|
||
<mc-diorama>
|
||
<canvas width="800" height="600"></canvas>
|
||
</mc-diorama>
|
||
```
|
||
|
||
### Performance Optimizations
|
||
|
||
1. **Lazy rendering** - Only update when content changes
|
||
2. **Visibility culling** - Skip off-screen dioramas
|
||
3. **State preservation** - Only rebuild meshes when blocks change
|
||
4. **Texture batching** - Single texture bind for all blocks
|
||
5. **Instanced drawing** - One draw call per block type
|
||
|
||
### Mobile-First Interaction
|
||
|
||
**Touch Handling**:
|
||
- 200ms delay before engaging (allows scroll detection)
|
||
- 1-finger: orbit (doesn't interfere with page scroll)
|
||
- 2-finger: pan + zoom (pinch gesture)
|
||
- Touch doesn't block page scroll unless clearly interacting
|
||
|
||
**Scroll Delegation**:
|
||
```javascript
|
||
canvas.addEventListener('touchstart', e => {
|
||
// Check if user is trying to scroll
|
||
if (maxTouchDistance < 10px) {
|
||
// Let browser handle scroll
|
||
return;
|
||
}
|
||
// Engage diorama interaction
|
||
e.preventDefault();
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Extensibility
|
||
|
||
### Adding New Block Types
|
||
|
||
1. **Blockstate JSON** - Define block variants in `public/assets/minecraft/blockstates/`
|
||
2. **Model JSON** - Define geometry in `public/assets/minecraft/models/`
|
||
3. **Texture PNG** - Add texture image in `public/assets/minecraft/textures/`
|
||
|
||
The system automatically discovers and loads these assets.
|
||
|
||
### Adding New Diorama Features
|
||
|
||
The controller system is designed for easy extension:
|
||
|
||
```javascript
|
||
class SpacetimeController {
|
||
constructor(diorama, options = {}) {
|
||
this.opts = {orbit: true, pan: true, zoom: true, ...options};
|
||
// Add new options here
|
||
}
|
||
}
|
||
```
|
||
|
||
New features can be added as optional behaviors without breaking existing dioramas.
|
||
|
||
---
|
||
|
||
## Development Workflow
|
||
|
||
### Running Locally
|
||
|
||
```bash
|
||
mise run dev
|
||
# Opens browser at localhost:5173
|
||
# Automatically reloads on changes to .typ or src/**/*
|
||
```
|
||
|
||
### Building for Production
|
||
|
||
```bash
|
||
mise run build
|
||
# Compiles Typst to .typ-bundle/
|
||
# Bundles with Vite to dist/
|
||
```
|
||
|
||
### Debugging
|
||
|
||
**Enable debug redraw**:
|
||
```javascript
|
||
window.mcEngine.debug_redraw = true;
|
||
// Highlights regions that are being redrawn
|
||
```
|
||
|
||
**Check resource loading**:
|
||
```javascript
|
||
window.mcEngine.cache.data
|
||
// Shows all loaded resources
|
||
```
|
||
|
||
**Inspect frame state**:
|
||
```javascript
|
||
const frame = document.querySelector('mc-diorama').frame;
|
||
console.log(frame.blocks, frame.currentTime);
|
||
```
|
||
|
||
---
|
||
|
||
## Future Architecture Changes
|
||
|
||
The JavaScript API is intentionally unstable. Planned changes:
|
||
|
||
1. **Component system** - Move from custom elements to framework components
|
||
2. **WebAssembly rendering** - Potential Rust/WASM renderer for better performance
|
||
3. **TypeScript migration** - Improve type safety and IDE support
|
||
4. **Animation system** - More sophisticated timeline interpolation
|
||
5. **Shader effects** - Enhanced visual feedback (glows, particles, etc.)
|
||
|
||
These changes won't break the Typst authoring experience—the diorama content format will remain the same.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- Typst documentation: https://typst.app/docs
|
||
- WebGL2 documentation: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API
|
||
- Minecraft Java Edition documentation: https://minecraft.wiki/w/Java_Edition
|
||
- Instanced rendering: https://learnopengl.com/Advanced-OpenGL/Instancing
|