Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 256 lines 7.1 kB view raw view rendered
1# 3D Text Rendering Plan (`sign`) 2 3## Goal 4Add a `sign` API that creates line-based Forms from text, allowing text labels in 3D space (like player names above camera frustums). 5 6**Naming Convention:** 7- 2D text: `write()` - writing on a flat surface 8- 3D text: `sign()` - placing a sign in physical space 9 10--- 11 12## Current 2D Text System Overview 13 14### Font Data Structure 15 16**BDF Pixel Fonts (MatrixChunky8):** 17```javascript 18{ 19 resolution: [width, height], // e.g., [4, 8] for a 4x8 character 20 offset: [x, y], // positioning offset 21 baselineOffset: [x, y], // baseline correction 22 advance: number, // spacing to next character 23 pixels: [ // 2D array of 1s and 0s 24 [0, 1, 1, 0], 25 [1, 0, 0, 1], 26 // ... 8 rows for 8px tall font 27 ] 28} 29``` 30 31**Vector Fonts (font_1):** 32```javascript 33{ 34 commands: [ 35 { name: "line", args: [x1, y1, x2, y2] }, 36 { name: "line", args: [x1, y1, x2, y2] }, 37 // ... line segments that draw the glyph 38 ], 39 resolution: [width, height], 40 offset: [x, y] 41} 42``` 43 44### 2D Rendering Flow 45 461. **`ink(r,g,b).write(text, pos, ...)`** - Entry point in disk.mjs 472. **Typeface class** (`type.mjs`) - Manages font loading and glyph lookup 483. **Graph rendering** (`graph.mjs`) - Draws glyphs: 49 - BDF fonts: Loop through `pixels` array, call `point()` for each `1` 50 - Vector fonts: Execute `commands` array, draw lines 51 52### Key Files 53- `/system/public/aesthetic.computer/lib/type.mjs` - Typeface class, glyph loading 54- `/system/public/aesthetic.computer/lib/graph.mjs` - `draw()` function renders glyphs 55- `/system/public/aesthetic.computer/disks/common/fonts.mjs` - Font metadata 56 57--- 58 59## 3D Text Implementation Plan 60 61### Option A: Convert Pixel Glyphs to Line-Based Forms (Recommended) 62 63For MatrixChunky8, convert each "on" pixel into a small line segment or cross pattern. 64 65**Approach:** 66```javascript 67// For each pixel at (col, row), create a small cross or dot representation 68// This creates a "dotted" text effect in 3D 69 70function text3D(text, options = {}) { 71 const { 72 font = "MatrixChunky8", 73 scale = 0.1, // World units per pixel 74 spacing = 0.05, // Extra space between chars 75 color = [1,1,1,1], 76 } = options; 77 78 const positions = []; 79 const colors = []; 80 81 let cursorX = 0; 82 83 for (const char of text) { 84 const glyph = getGlyph(char, font); 85 86 if (glyph.pixels) { 87 // BDF font - convert pixels to 3D points/crosses 88 for (let row = 0; row < glyph.pixels.length; row++) { 89 for (let col = 0; col < glyph.pixels[row].length; col++) { 90 if (glyph.pixels[row][col] === 1) { 91 const x = cursorX + col * scale; 92 const y = -row * scale; // Y flipped (text reads top-down) 93 const z = 0; 94 95 // Create small cross at this point 96 const s = scale * 0.4; // Cross size 97 // Horizontal line segment 98 positions.push([x - s, y, z, 1], [x + s, y, z, 1]); 99 colors.push(color, color); 100 // Vertical line segment 101 positions.push([x, y - s, z, 1], [x, y + s, z, 1]); 102 colors.push(color, color); 103 } 104 } 105 } 106 } 107 108 cursorX += (glyph.advance || glyph.resolution?.[0] || 4) * scale + spacing; 109 } 110 111 return new Form( 112 { type: "line", positions, colors }, 113 { pos: [0, 0, 0], scale: 1 } 114 ); 115} 116``` 117 118### Option B: Outline/Stroke Glyphs (More Complex) 119 120Convert pixel boundaries to connected line segments forming character outlines. 121- More visually appealing but significantly more complex 122- Would need edge detection algorithm on pixel grid 123- Better for large text 124 125### Option C: Use Vector Font Data 126 127If a vector font (like font_1) is available, extract the line commands directly: 128 129```javascript 130if (glyph.commands) { 131 // Vector font - extract line segments directly 132 for (const cmd of glyph.commands) { 133 if (cmd.name === "line") { 134 const [x1, y1, x2, y2] = cmd.args; 135 positions.push( 136 [cursorX + x1 * scale, -y1 * scale, 0, 1], 137 [cursorX + x2 * scale, -y2 * scale, 0, 1] 138 ); 139 colors.push(color, color); 140 } 141 } 142} 143``` 144 145--- 146 147## Proposed API 148 149### In `graph.mjs` - Add `sign()` Form Generator 150 151```javascript 152// Create a 3D text Form that can be positioned/rotated in world space 153const nameLabel = sign("Player1", { 154 font: "MatrixChunky8", 155 scale: 0.05, // Size in world units 156 color: [0, 1, 0, 1], // RGBA 157 align: "center", // left, center, right 158 style: "dots", // dots, crosses, outline (future) 159}); 160 161// Position it in 3D 162nameLabel.position = [px, py + 0.5, pz]; // Above player 163nameLabel.rotation = [0, yaw, 0]; // Face camera direction (billboard?) 164 165// Render 166ink(255, 255, 255).form(nameLabel); 167``` 168 169### Alternative: Chained API via `ink()` 170 171```javascript 172// Direct rendering with ink chain 173ink(0, 255, 0).sign("Player1", [px, py + 0.5, pz], { scale: 0.05 }); 174``` 175 176### Billboard Option (Always Face Camera) 177 178For labels that should always face the viewer: 179 180```javascript 181const label = sign("Name", { billboard: true }); 182// In paint(), before rendering: 183label.rotation = [0, -cameraYaw, 0]; // Counter-rotate to face camera 184``` 185 186--- 187 188## Implementation Steps 189 190### Phase 1: Basic `sign()` Function 1911. Add `sign()` function to graph.mjs (exported for piece use) 1922. Accept font name, get glyph data from Typeface 1933. Convert pixel data to line positions 1944. Return Form with line geometry 195 196### Phase 2: Integration with `ink().sign()` 1971. Add `sign()` to the ink chain in disk.mjs 1982. Handle Form creation and rendering in one call 1993. Support same parameters as 2D write 200 201### Phase 3: Enhancements 2021. Add billboard mode (auto-rotate to face camera) 2032. Add outline rendering style 2043. Add text measuring (get width before rendering) 2054. Support multi-line text 206 207--- 208 209## Usage Example in 1v1.mjs 210 211```javascript 212// In boot(): 213function boot({ Form, sign, glyphs, ... }) { 214 globalSign = sign; 215 globalGlyphs = glyphs; 216} 217 218// When player joins, create name sign: 219playerBoxes[id] = { 220 // ... other Forms ... 221 nameSign: globalSign(content.handle || id.slice(0, 6), { 222 scale: 0.03, 223 color: playerColor, 224 align: "center", 225 glyphs: globalGlyphs?.("MatrixChunky8") || {}, 226 }), 227}; 228 229// In paint(): 230const label = playerModel.nameSign; 231if (label) { 232 label.position = [px, py + 0.35, pz]; // Above camera box 233 // Billboard: face toward viewer 234 label.rotation = [0, (self.rot?.y || 0) + 180, 0]; 235 ink(255, 255, 255).form(label); 236} 237``` 238 239--- 240 241## Technical Notes 242 243### Glyph Access 244- Glyphs are loaded async via Typeface class 245- For MatrixChunky8, glyphs load from BDF endpoint: `/api/bdf-glyph?char=X` 246- Need to handle missing glyphs gracefully (use `?` fallback) 247 248### Performance Considerations 249- Cache generated text Forms (don't recreate every frame) 250- Only recreate when text changes 251- Consider LOD (level of detail) - simpler geometry at distance 252 253### Coordinate System 254- Text is generated in local space (centered or left-aligned at origin) 255- Y is flipped (negative Y goes down in screen space) 256- Z=0 is the text plane