Monorepo for Aesthetic.Computer
aesthetic.computer
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