518 lines
23 KiB
Markdown
518 lines
23 KiB
Markdown
# Visual Programming Framework
|
||
|
||
A browser-based flowchart-to-C code generator with typed measurement system, procedure libraries, and live MathJax-rendered expressions.
|
||
|
||
## Architecture
|
||
|
||
The app is built as vanilla HTML/CSS/JS with no framework. All state lives in the DOM (custom elements) and is serialised to localStorage.
|
||
|
||
### File dependency order (bottom to top)
|
||
|
||
```
|
||
index.html ← entry point, templates, page layout
|
||
│
|
||
dom.js ← $, $id, svgNamespace, STORAGE_KEY (global constants)
|
||
helpers.js ← data-source lookups, string escaping, dropdown refresh
|
||
error.js ← modal error overlay
|
||
draw.js ← SVG path routing, link interaction, path deletion
|
||
│
|
||
wcblock.js ← WCBlock base class (drag/drop, link system)
|
||
blocks-algo.js ← Algorithm tab blocks (WCStart, WCEnd, WCOutput, WCDecision,
|
||
│ WCAssign, WCExpression, WCVarRef)
|
||
blocks-data.js ← Data & Measurement tab blocks (WCQuantity, WCUnit, WCBoolean,
|
||
│ WCScalar, WCString, WCArray)
|
||
blocks-proc.js ← Procedure blocks (WCProcedure, WCProcCall)
|
||
wcprogram.js ← WCProgram + block registry + registration calls
|
||
│
|
||
persist.js ← localStorage save/load/new
|
||
app.js ← bootstraps the workspace, creates defaults, wires UI
|
||
```
|
||
|
||
### Module overview
|
||
|
||
| File | Lines | Exports | Purpose |
|
||
|------|-------|---------|---------|
|
||
| `dom.js` | 23 | `$`, `$id`, `svgNamespace`, `STORAGE_KEY` | Query shortcuts and app-wide constants |
|
||
| `helpers.js` | ~240 | 12 functions | Data-source lookup, string escaping, dropdown refresh utilities |
|
||
| `error.js` | 32 | `showError`, `closeError` | Error overlay modal |
|
||
| `draw.js` | 208 | 6 functions | SVG path routing, shadow link, link creation/deletion |
|
||
| `wcblock.js` | ~236 | `WCBlock` | Base class: drag/drop, link system, code traversal |
|
||
| `blocks-algo.js` | ~460 | 7 classes | Algorithm tab blocks (start, end, output, decision, assign, expression, varref) |
|
||
| `blocks-data.js` | ~570 | 6 classes | Data & Measurement tab blocks (quantity, unit, boolean, scalar, string, array) |
|
||
| `blocks-proc.js` | ~280 | 2 classes | Procedure blocks (definition, call) |
|
||
| `wcprogram.js` | ~370 | `WCProgram` + registry | Root container, ID counter, node factory, code generation, block registration |
|
||
| `persist.js` | ~245 | `saveWorkspace`, `loadWorkspace`, `newWorkspace`, helpers | localStorage serialisation |
|
||
| `app.js` | ~390 | 7 functions + `main()` | Application bootstrap, OpenGL defaults, event wiring |
|
||
| `style.css` | ~480 | — | All styles |
|
||
| `index.html` | ~380 | — | Page layout + `<template>` definitions for all block types |
|
||
|
||
## Block types
|
||
|
||
All live blocks are custom elements registered via `customElements.define()`. Each block type has a `<template id="wc-xxx">` in `index.html`.
|
||
|
||
### Flowchart blocks (Algorithm tab)
|
||
|
||
| Tag | Class | Purpose | Hooks |
|
||
|-----|-------|---------|-------|
|
||
| `wc-start` | WCStart | Program entry point | 1 logic out |
|
||
| `wc-end` | WCEnd | Program termination | 1 logic in |
|
||
| `wc-output` | WCOutput | `printf()` statement | 1 logic in, 1 logic out |
|
||
| `wc-decision` | WCDecision | `if/else` branch | 1 logic in, 2 logic out (yes/no), 1 data in (boolean) |
|
||
| `wc-varref` | WCVarRef | Read-only variable reference | 1 data out |
|
||
| `wc-expression` | WCExpression | Math expression with live LaTeX | N data in (dynamic vars), 1 data out |
|
||
| `wc-assign` | WCAssign | Variable assignment | 1 logic in, 1 logic out, 1 data in |
|
||
| `wc-proccall` | WCProcCall | Procedure call | 1 logic in, 1 logic out, N data in/out (dynamic params) |
|
||
|
||
### Measurement blocks (Measurements tab)
|
||
|
||
| Tag | Class | Purpose |
|
||
|-----|-------|---------|
|
||
| `wc-quantity` | WCQuantity | Defines a quantity type (7 SI dimensions + range + default unit) |
|
||
| `wc-unit` | WCUnit | Defines a unit with recursive SI factor resolution |
|
||
|
||
### Variable blocks (Data tab)
|
||
|
||
| Tag | Class | Purpose | dataType |
|
||
|-----|-------|---------|----------|
|
||
| `wc-boolean` | WCBoolean | Boolean variable (`int` in C) | `"boolean"` |
|
||
| `wc-scalar` | WCScalar | Typed scalar with unit/SI conversion | `"scalar"` |
|
||
| `wc-string` | WCString | String variable (char*/length struct) | `"string"` |
|
||
| `wc-array` | WCArray | Typed C array with comma-separated values | `"array"` |
|
||
|
||
### Procedure blocks (Procedures tab)
|
||
|
||
| Tag | Class | Purpose |
|
||
|-----|-------|---------|
|
||
| `wc-procedure` | WCProcedure | Procedure definition with typed params |
|
||
| `wc-proccall` | WCProcCall | Call to a defined procedure |
|
||
|
||
### Container block (Algorithm tab)
|
||
|
||
| Tag | Class | Purpose |
|
||
|-----|-------|---------|
|
||
| `wc-program` | WCProgram | Root container: ID counter, node factory, code generation |
|
||
|
||
## Key mechanisms
|
||
|
||
### Class hierarchy
|
||
|
||
```
|
||
HTMLElement
|
||
├── WCBlock (base: drag/drop, link system, code traversal)
|
||
│ ├── WCStart, WCEnd, WCOutput
|
||
│ ├── WCDecision
|
||
│ ├── WCQuantity, WCUnit
|
||
│ ├── WCBoolean, WCScalar, WCString, WCArray
|
||
│ ├── WCVarRef, WCExpression
|
||
│ ├── WCAssign
|
||
│ ├── WCProcedure, WCProcCall
|
||
└── WCProgram (not a WCBlock — no drag/link)
|
||
```
|
||
|
||
### Two-click linking
|
||
|
||
1. Click an output hook → starts a shadow (dashed) path that follows the cursor
|
||
2. Click a compatible input hook → creates a persistent SVG path
|
||
3. Click an existing path → deletes it
|
||
4. Click the draw-area background → cancels the pending link
|
||
|
||
Type checking is enforced: logic hooks only connect to logic hooks, data hooks only to data hooks of the same `dataType`.
|
||
|
||
### Grid-snapped drag
|
||
|
||
Blocks use CSS `--grid-size` (25px) for snap-to-grid positioning. All connection paths are redrawn in real-time during drag via `drawpath()`.
|
||
|
||
### 45°-constrained path routing
|
||
|
||
Paths use `routeBetween()` which produces SVG polylines constrained to 0°, 45°, and 90° angles. Each path extends `EXT` (25px) from the source hook in its exit direction before routing.
|
||
|
||
### Persistence
|
||
|
||
All workspace state is serialised to `localStorage` under key `"workspace"`:
|
||
- Block positions + type-specific data
|
||
- Connection topology (by block ID + hook ID)
|
||
- ID counter for generating unique block IDs
|
||
|
||
The `loadWorkspace()` function follows a strict 7-step order:
|
||
1. Tear down existing state
|
||
2. Restore measurements (types + units)
|
||
3. Restore procedures and library tabs
|
||
4. Restore algorithm blocks (reposition start/end, recreate others)
|
||
5. Restore data/variable blocks
|
||
6. Restore SVG connections (hook resolution uses IDs or fallbacks)
|
||
7. Refresh all dropdowns and SI displays
|
||
|
||
### MathJax expression rendering
|
||
|
||
WCExpression uses `MathJax.typesetPromise()` to render LaTeX as rendered HTML. Variable hooks are rebuilt synchronously (before the async MathJax call) so that connection restoration can find them by deterministic ID (`"wc-expr-N-vh-varname"`).
|
||
|
||
## Code generation
|
||
|
||
`WCProgram.generateCode()` produces a complete C source file:
|
||
|
||
```
|
||
// --- WiseCode® ---
|
||
#include <library.h> // for referenced procedure libraries
|
||
#include <stdlib.h>
|
||
#include <string.h> // if string variables exist
|
||
|
||
int main() {
|
||
// Type definitions (typedef struct for used quantities)
|
||
// Unit definitions (comments)
|
||
// string struct definition (if needed)
|
||
// Variable declarations (booleans, scalars, strings, arrays)
|
||
// Expression declarations
|
||
// Flowchart body (start → end traversal)
|
||
// Free string allocations
|
||
return 0;
|
||
}
|
||
// --- END ---
|
||
```
|
||
|
||
The `generateCode(visited)` method on each block uses a `Set` to track already-emitted blocks, handling fan-out convergence (when two paths merge).
|
||
|
||
### Code generation for OpenGL procedures
|
||
|
||
When the workflow includes `wc-proccall` blocks referencing a library like `gl`, `glfw`, or `glad`, the generated code includes:
|
||
|
||
```c
|
||
#include <gl.h>
|
||
#include <glfw.h>
|
||
#include <glad.h>
|
||
```
|
||
|
||
The include path uses the library name directly (`<gl.h>`, `<glfw.h>`, `<glad.h>`). Real OpenGL headers use different paths (e.g. `<GLFW/glfw3.h>`). Adjust the includes in the generated output to match your build environment.
|
||
|
||
**Output parameter convention**: All procedure output parameters are generated with a `&` prefix in the C call, e.g.:
|
||
- `glCreateShader(GL_VERTEX_SHADER, &shader);` — generated when `shader` is an output param
|
||
- `glGenBuffers(1, &VBO);` — generated when `VBO` is an output param
|
||
|
||
For OpenGL functions that natively return values (like `glCreateShader` which returns `GLuint`), the generated code uses a pointer-output convention. A thin wrapper function can adapt if needed:
|
||
|
||
## SI unit system
|
||
|
||
Units form a directed acyclic graph rooted at SI base units:
|
||
- Each unit stores an SI definition string (e.g. `"0.0254"` or `"12 in"`)
|
||
- `getSiFactor()` recursively resolves the conversion factor with cycle detection
|
||
- Scalars auto-convert to SI values for code generation
|
||
- The unit dropdown on scalars filters by the selected quantity type
|
||
|
||
## Shared utility functions (helpers.js)
|
||
|
||
| Function | Purpose | Used by |
|
||
|----------|---------|---------|
|
||
| `splitValueAndUnit()` | Parse "5 km" → `{numVal, unitSuffix}` | WCScalar, WCUnit |
|
||
| `rebuildSelect()` | Replace `<select>` options preserving selection | All refresh functions |
|
||
| `refreshQuantityOptions()` | Sync quantity-name dropdowns | WCQuantity, setup |
|
||
| `refreshUnitOptions()` | Sync unit-suffix dropdowns | WCScalar, setup |
|
||
| `refreshDefaultUnitOptions()` | Sync default-unit dropdowns | WCQuantity, setup |
|
||
| `getDefaultUnitSuffix()` | Lookup default unit for a quantity | WCScalar |
|
||
| `refreshVarRefOptions()` | Sync variable-select dropdowns | WCVarRef, WCAssign, setup |
|
||
| `refreshProcOptions()` | Sync procedure-select dropdowns | WCProcCall, setup |
|
||
| `switchLibTab()` | Activate a library sub-tab | UI |
|
||
| `findDataSource()` | Find block connected to a data hook | WCDecision, WCAssign, WCProcCall |
|
||
| `escapeString()` | Escape chars for C string literals | WCString, WCAssign |
|
||
| `refreshAll()` | Run all refresh passes in order | Various |
|
||
|
||
## CSS architecture
|
||
|
||
- CSS custom properties for theming (`--grid-size`, `--bg`, `--accent`, etc.)
|
||
- Each block type has a distinct background/border color pair
|
||
- Hooks use absolute positioning with `data-direction` and `data-exit` attributes
|
||
- SVG connection layer sits below blocks (`z-index: 0`)
|
||
- Grid background via CSS `background-image` linear-gradient
|
||
|
||
## Array data type (wc-array)
|
||
|
||
The `wc-array` custom element provides a typed C array declaration in the Data tab with a tabular element editor. It generates `type name[] = {values};`.
|
||
|
||
### Tabular element editor
|
||
|
||
Elements are displayed as a table of rows inside the block:
|
||
|
||
```
|
||
┌───────────────────────────────────────────┐
|
||
│ [arr ] [float ▼] [] 9 elements │
|
||
├───────────────────────────────────────────┤
|
||
│ 0 [ -0.5 ] [×] │
|
||
│ 1 [ -0.5 ] [×] │
|
||
│ 2 [ 0.0 ] [×] │
|
||
│ ... │
|
||
│ 8 [ 0.0 ] [×] │
|
||
│ 9 [ ] │ ← empty row for new elements
|
||
└───────────────────────────────────────────┘
|
||
```
|
||
|
||
Each element row shows:
|
||
- **Index badge**: zero-based element number (monospace, muted colour)
|
||
- **Value input**: editable text field for the element's literal value
|
||
- **Delete button** (×): removes that element and renumbers all subsequent rows
|
||
|
||
### Adding elements
|
||
|
||
The bottom row of the table is always an empty input. To add a new element:
|
||
1. Type the value into the empty input
|
||
2. Press **Enter**, or **tab/blur** away from the input
|
||
3. The value is appended to the array and a new empty row appears
|
||
|
||
### Removing elements
|
||
|
||
Click the **×** button on any row. The element is removed and all remaining rows are re-indexed starting from 0.
|
||
|
||
### Element count label
|
||
|
||
The header row shows a live count label (e.g. `"9 elements"`, `"1 element"`). This updates immediately when elements are added or removed.
|
||
|
||
### Template (`index.html`)
|
||
|
||
```html
|
||
<template id="wc-array">
|
||
<wc-array>
|
||
<div class="array-row">
|
||
<input class="var-name" placeholder="name" value="arr">
|
||
<select class="array-type">
|
||
<option value="float">float</option>
|
||
<option value="double">double</option>
|
||
<option value="int">int</option>
|
||
<option value="unsigned int">unsigned int</option>
|
||
<option value="char">char</option>
|
||
</select>
|
||
<span class="array-bracket">[]</span>
|
||
<span class="array-count">0 elements</span>
|
||
</div>
|
||
<div class="array-elements">
|
||
<!-- Element rows rendered dynamically by WCArray._renderElements() -->
|
||
</div>
|
||
</wc-array>
|
||
</template>
|
||
```
|
||
|
||
Fields:
|
||
- **var-name**: the C identifier for the array (default: `"arr"`)
|
||
- **array-type**: the C element type selector (float, double, int, unsigned int, char)
|
||
- **array-count**: auto-updating label showing the element count
|
||
- **array-elements**: container for the dynamic element rows (populated by JS)
|
||
|
||
### Class (blocks-data.js)
|
||
|
||
```js
|
||
class WCArray extends WCBlock {
|
||
get dataType() { return "array"; }
|
||
|
||
constructor() { super(); this._values = []; }
|
||
connectedCallback() { /* binds DOM refs + renders initial state */ }
|
||
|
||
// Row rendering
|
||
_renderElements() { /* rebuilds all rows from this._values */ }
|
||
_makeElementRow(i) { /* creates one populated value row */ }
|
||
_makeNewRow() { /* creates the empty add-element row */ }
|
||
|
||
// Mutation
|
||
_addElement(val) { this._values.push(val); this._renderElements(); }
|
||
_removeElement(idx) { this._values.splice(idx, 1); this._renderElements(); }
|
||
_updateCount() { /* sets the count label text */ }
|
||
|
||
// Serialisation
|
||
saveData() → { name, type, values: [...] }
|
||
restoreData(d) → { /* handles both string (legacy) and array format */ }
|
||
|
||
// Code gen
|
||
generateOwnCode() → "type name[] = {val1, val2, ...};"
|
||
}
|
||
```
|
||
|
||
Key behaviours:
|
||
- `_values` is an internal `Array` of strings, one per element
|
||
- `_renderElements()` is called after every mutation and from `connectedCallback` (via `setTimeout`) and `restoreData`
|
||
- Focus position is preserved across `_renderElements()` calls so editing feels seamless
|
||
- Event handlers use `input.closest()` / `delBtn.closest()` to safely identify the row index from the DOM
|
||
|
||
### Code generation
|
||
|
||
| Input | Generated C |
|
||
|-------|-------------|
|
||
| name=`"vertices"`, type=`"float"`, values=`["-0.5","-0.5","0.0","0.5","-0.5","0.0","0.0","0.5","0.0"]` | `float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};` |
|
||
| name=`"indices"`, type=`"unsigned int"`, values=`["0","1","2","1","2","3"]` | `unsigned int indices[] = {0, 1, 2, 1, 2, 3};` |
|
||
| name=`"empty"`, type=`"float"`, values=`[]` | `float empty[1];` |
|
||
|
||
Values are joined with `, ` directly (no comma-splitting needed — already individual strings in the array).
|
||
|
||
### Persistence
|
||
|
||
`saveData()` serialises `_values` as a proper JSON array:
|
||
```json
|
||
{ "name": "vertices", "type": "float", "values": ["-0.5", "-0.5", "0.0"] }
|
||
```
|
||
|
||
`restoreData()` handles both the current array format and the legacy string format (comma-separated), so old saved workspaces remain compatible.
|
||
|
||
### Variable reference integration
|
||
|
||
Arrays are scanned by `refreshVarRefOptions()` alongside booleans, scalars, and strings. A `wc-varref` block can reference an array from its dropdown, setting the data hook type to `"array"`. The referenced array name can then be typed into procedure call text inputs (e.g. `glBufferData`'s `data` parameter).
|
||
|
||
### No data hook
|
||
|
||
Unlike `wc-boolean`, `wc-scalar`, and `wc-string`, the `wc-array` block does **not** include a `.hook` button. Arrays are referenced by name directly in procedure call arguments, not linked via data-flow connections.
|
||
|
||
### Default vertex array
|
||
|
||
When the app starts fresh (no saved workspace), `createDefaultOpenGLProcedures()` creates a `wc-array` in the Data tab with Hello Triangle vertex data:
|
||
|
||
```
|
||
┌──────────────────────────────────────┐
|
||
│ [vertices] [float ▼] [] 9 elements │
|
||
├──────────────────────────────────────┤
|
||
│ 0 [-0.5] [×] │
|
||
│ 1 [-0.5] [×] │
|
||
│ 2 [ 0.0] [×] │
|
||
│ 3 [ 0.5] [×] │
|
||
│ 4 [-0.5] [×] │
|
||
│ 5 [ 0.0] [×] │
|
||
│ 6 [ 0.0] [×] │
|
||
│ 7 [ 0.5] [×] │
|
||
│ 8 [ 0.0] [×] │
|
||
│ 9 [ ] │
|
||
└──────────────────────────────────────┘
|
||
```
|
||
|
||
Produces:
|
||
```c
|
||
float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};
|
||
```
|
||
|
||
Three vertices, each with (x, y, z) = 9 float values total.
|
||
|
||
## OpenGL default procedures
|
||
|
||
On first load, `createDefaultOpenGLProcedures()` populates three library tabs in the Procedures pane with everything needed for a Hello Triangle pipeline.
|
||
|
||
### Library: glfw (window management)
|
||
|
||
| Procedure | Inputs | Outputs | C signature |
|
||
|-----------|--------|---------|-------------|
|
||
| `glfwInit` | — | `result: int` | `int glfwInit(void)` |
|
||
| `glfwCreateWindow` | `width: int`, `height: int`, `title: string` | `window: int` | `GLFWwindow* glfwCreateWindow(int, int, const char*, ...)` |
|
||
| `glfwMakeContextCurrent` | `window: int` | — | `void glfwMakeContextCurrent(GLFWwindow*)` |
|
||
| `glfwSwapBuffers` | `window: int` | — | `void glfwSwapBuffers(GLFWwindow*)` |
|
||
| `glfwPollEvents` | — | — | `void glfwPollEvents(void)` |
|
||
| `glfwWindowShouldClose` | `window: int` | `result: int` | `int glfwWindowShouldClose(GLFWwindow*)` |
|
||
| `glfwTerminate` | — | — | `void glfwTerminate(void)` |
|
||
|
||
Generated code uses `int` for the `GLFWwindow*` handle (stored as a pointer-sized integer). Cast as needed.
|
||
|
||
### Library: glad (OpenGL loader)
|
||
|
||
| Procedure | Inputs | Outputs | C signature |
|
||
|-----------|--------|---------|-------------|
|
||
| `gladLoadGL` | — | `result: int` | `int gladLoadGL(void)` |
|
||
|
||
### Library: gl (OpenGL core — Hello Triangle pipeline)
|
||
|
||
| Procedure | Inputs | Outputs |
|
||
|-----------|--------|---------|
|
||
| `glCreateShader` | `type: unsigned int` | `shader: unsigned int` |
|
||
| `glShaderSource` | `shader: unsigned int`, `count: int`, `source: string` | — |
|
||
| `glCompileShader` | `shader: unsigned int` | — |
|
||
| `glGetShaderiv` | `shader: unsigned int`, `pname: unsigned int` | `params: int` |
|
||
| `glCreateProgram` | — | `program: unsigned int` |
|
||
| `glAttachShader` | `program: unsigned int`, `shader: unsigned int` | — |
|
||
| `glLinkProgram` | `program: unsigned int` | — |
|
||
| `glGetProgramiv` | `program: unsigned int`, `pname: unsigned int` | `params: int` |
|
||
| `glGenVertexArrays` | `n: unsigned int` | `arrays: unsigned int` |
|
||
| `glBindVertexArray` | `array: unsigned int` | — |
|
||
| `glGenBuffers` | `n: unsigned int` | `buffers: unsigned int` |
|
||
| `glBindBuffer` | `target: unsigned int`, `buffer: unsigned int` | — |
|
||
| `glBufferData` | `target: unsigned int`, `size: int`, `data: string`, `usage: unsigned int` | — |
|
||
| `glVertexAttribPointer` | `index: unsigned int`, `size: int`, `type: unsigned int`, `normalized: int`, `stride: int`, `pointer: int` | — |
|
||
| `glEnableVertexAttribArray` | `index: unsigned int` | — |
|
||
| `glUseProgram` | `program: unsigned int` | — |
|
||
| `glDrawArrays` | `mode: unsigned int`, `first: int`, `count: int` | — |
|
||
| `glClear` | `mask: unsigned int` | — |
|
||
| `glClearColor` | `red: float`, `green: float`, `blue: float`, `alpha: float` | — |
|
||
| `glDeleteShader` | `shader: unsigned int` | — |
|
||
| `glDeleteProgram` | `program: unsigned int` | — |
|
||
| `glViewport` | `x: int`, `y: int`, `width: int`, `height: int` | — |
|
||
|
||
### Using the procedures in a Hello Triangle workflow
|
||
|
||
A typical flowchart from START to END:
|
||
|
||
1. **`glfw/glfwInit`** → output `result` connected to a decision or ignored
|
||
2. **`glfw/glfwCreateWindow`** → input `width: 800`, `height: 600`, `title: "Hello Triangle"` → output `window`
|
||
3. **`glfw/glfwMakeContextCurrent`** → input `window` from step 2
|
||
4. **`glad/gladLoadGL`** → output `result`
|
||
5. **`gl/glCreateShader`** → input `type: 35633` (`GL_VERTEX_SHADER`) → output `shader`
|
||
6. **`gl/glShaderSource`** → input `shader` from step 5, `count: 1`, `source: "..."` (vertex source)
|
||
7. **`gl/glCompileShader`** → input `shader` from step 5
|
||
8. Repeat steps 5–7 for fragment shader (`type: 35632` = `GL_FRAGMENT_SHADER`)
|
||
9. **`gl/glCreateProgram`** → output `program`
|
||
10. **`gl/glAttachShader`** → input `program`, `shader` (vertex)
|
||
11. **`gl/glAttachShader`** → input `program`, `shader` (fragment)
|
||
12. **`gl/glLinkProgram`** → input `program`
|
||
13. **`gl/glGenVertexArrays`** → input `n: 1` → output `VAO`
|
||
14. **`gl/glBindVertexArray`** → input `array: VAO`
|
||
15. **`gl/glGenBuffers`** → input `n: 1` → output `VBO`
|
||
16. **`gl/glBindBuffer`** → input `target: 34962` (`GL_ARRAY_BUFFER`), `buffer: VBO`
|
||
17. **`gl/glBufferData`** → input `target: 34962`, `size: 9*4` (or `sizeof(vertices)`), `data: vertices`, `usage: 35044` (`GL_STATIC_DRAW`)
|
||
18. **`gl/glVertexAttribPointer`** → input `index: 0`, `size: 3`, `type: 5126` (`GL_FLOAT`), `normalized: 0`, `stride: 0`, `pointer: 0`
|
||
19. **`gl/glEnableVertexAttribArray`** → input `index: 0`
|
||
20. **Loop**: `glClear`, `glUseProgram`, `glBindVertexArray`, `glDrawArrays`, `glfwSwapBuffers`, `glfwPollEvents`
|
||
21. **`glfw/glfwWindowShouldClose`** → input `window` → output `result` drives the loop decision
|
||
22. Cleanup: `glDeleteProgram`, `glDeleteShader`, `glfwTerminate`
|
||
|
||
### Generated C example (simplified)
|
||
|
||
```c
|
||
#include <gl.h>
|
||
#include <glfw.h>
|
||
#include <glad.h>
|
||
|
||
int main() {
|
||
float vertices[] = {-0.5, -0.5, 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, 0.0};
|
||
|
||
int result;
|
||
int window;
|
||
unsigned int shader;
|
||
unsigned int program;
|
||
unsigned int VAO;
|
||
unsigned int VBO;
|
||
|
||
glfwInit(&result);
|
||
glfwCreateWindow(800, 600, "Hello Triangle", &window);
|
||
glfwMakeContextCurrent(window);
|
||
gladLoadGL(&result);
|
||
glCreateShader(35633, &shader);
|
||
glShaderSource(shader, 1, vertexSource);
|
||
glCompileShader(shader);
|
||
// ... fragment shader, program linking, VAO/VBO setup ...
|
||
glClear(16640);
|
||
glUseProgram(program);
|
||
glBindVertexArray(VAO);
|
||
glDrawArrays(4, 0, 3);
|
||
glfwSwapBuffers(window);
|
||
glfwPollEvents();
|
||
// ... loop ...
|
||
glfwTerminate();
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
> **Note**: Output parameters use `&` prefix. For functions like `glCreateShader` that natively return values, the generated code uses a pointer-output convention. Adjust the function signatures or add thin wrappers to match the real OpenGL API.
|
||
|
||
### Include path mapping
|
||
|
||
| Library name | Generated include | Real include (adjust manually) |
|
||
|-------------|-------------------|--------------------------------|
|
||
| `glfw` | `#include <glfw.h>` | `#include <GLFW/glfw3.h>` |
|
||
| `glad` | `#include <glad.h>` | `#include <glad/glad.h>` |
|
||
| `gl` | `#include <gl.h>` | Included by glad, or `#include <GL/gl.h>` |
|
||
|
||
The generated output uses the library name directly. Adjust the `#include` paths in the generated C code to match your project's OpenGL setup.
|
||
|
||
## Development notes
|
||
|
||
- No build step — edit files directly, refresh browser
|
||
- Script loading order in `index.html` matches the dependency order above
|
||
- New block types: add a `<template>`, create a class extending `WCBlock`, register with `registerBlock()`
|
||
- All functions are global (no modules/imports) — naming conventions and JSDoc serve as documentation
|