imgui implementation
This commit is contained in:
@@ -53,10 +53,16 @@ FetchContent_Declare(
|
||||
|
||||
# ---- Frontend dependencies (used by tools/) ----------------------------------
|
||||
|
||||
# SDL2 – install via your system package manager, e.g.:
|
||||
# sudo apt install libsdl2-dev (Debian/Ubuntu)
|
||||
# brew install sdl2 (macOS)
|
||||
find_package(SDL2 QUIET)
|
||||
# GLFW – windowing for the node-editor tool
|
||||
FetchContent_Declare(
|
||||
glfw
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 3.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Dear ImGui
|
||||
FetchContent_Declare(
|
||||
@@ -74,9 +80,15 @@ FetchContent_Declare(
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
# Make available what we need right now.
|
||||
# imgui / imnodes / SDL2 / FastNoise2 are deferred until tools/ is built.
|
||||
FetchContent_MakeAvailable(flecs doctest glm nlohmann_json)
|
||||
# ImGuiFileDialog – file browser widget for the node-editor tool
|
||||
FetchContent_Declare(
|
||||
ImGuiFileDialog
|
||||
GIT_REPOSITORY https://github.com/aiekick/ImGuiFileDialog.git
|
||||
GIT_TAG v0.6.7
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(flecs doctest glm nlohmann_json glfw imgui imnodes)
|
||||
|
||||
# ---- Core library ------------------------------------------------------------
|
||||
|
||||
@@ -125,3 +137,7 @@ target_link_libraries(factory-hole-tests PRIVATE
|
||||
)
|
||||
|
||||
add_test(NAME factory-hole-tests COMMAND factory-hole-tests)
|
||||
|
||||
# ---- Tools -------------------------------------------------------------------
|
||||
|
||||
add_subdirectory(tools/node-editor)
|
||||
|
||||
43
graph.json
Normal file
43
graph.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"editor": {
|
||||
"nodePositions": {
|
||||
"1": [
|
||||
-170.0,
|
||||
-162.0
|
||||
],
|
||||
"__worldOutput": [
|
||||
400.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"previewOriginX": 0,
|
||||
"previewOriginY": 0,
|
||||
"previewScale": 1.0,
|
||||
"seed": 0,
|
||||
"tileRegistry": [
|
||||
{
|
||||
"color": [
|
||||
0.7348794341087341,
|
||||
0.730800986289978,
|
||||
0.7554347515106201
|
||||
],
|
||||
"id": 1,
|
||||
"name": "Stone"
|
||||
}
|
||||
],
|
||||
"worldOutputPasses": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"graph": {
|
||||
"connections": [],
|
||||
"nextId": 2,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"tileId": 1,
|
||||
"type": "TileID"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
36
imgui.ini
Normal file
36
imgui.ini
Normal file
@@ -0,0 +1,36 @@
|
||||
[Window][Node Canvas]
|
||||
Pos=200,18
|
||||
Size=1080,702
|
||||
|
||||
[Window][Settings]
|
||||
Pos=0,18
|
||||
Size=200,702
|
||||
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
|
||||
[Window][Save Graph##SaveFileDlg]
|
||||
Pos=323,94
|
||||
Size=600,400
|
||||
|
||||
[Window][Open Graph##OpenFileDlg]
|
||||
Pos=60,60
|
||||
Size=600,400
|
||||
|
||||
[Table][0x4ED31530,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x837FA078,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x80A59430,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0xF2EF1CF5,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
213
include/WorldGraph/README.md
Normal file
213
include/WorldGraph/README.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# WorldGraph
|
||||
|
||||
A node-based procedural tile generation system. Graphs are composed of typed nodes wired together; evaluating the graph at a given world cell produces a tile ID.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
World generation is expressed as a **directed acyclic graph (DAG)** of computation nodes. Each node takes zero or more typed inputs, performs some computation, and produces a single typed output. The graph is evaluated independently **per cell** — there is no shared mutable state between cells.
|
||||
|
||||
The final result is an integer **tile ID**, obtained by calling `AsInt()` on whatever value the output node produces.
|
||||
|
||||
---
|
||||
|
||||
## Core Types (`WorldGraphTypes.h`)
|
||||
|
||||
### `Value`
|
||||
|
||||
A tagged union that can hold an `Int`, `Float`, or `Bool`. Values are coerced automatically when a node requests a different type (e.g. `AsFloat()` on an Int just casts it). This means connections between different types are permissive at runtime, though the editor can flag them.
|
||||
|
||||
### `EvalContext`
|
||||
|
||||
The per-cell context forwarded to every node during evaluation:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `worldX`, `worldY` | World-space tile coordinates of the cell being generated |
|
||||
| `seed` | World seed for deterministic noise |
|
||||
| `prevTiles` | Flat row-major array of tile IDs from the previous generation pass |
|
||||
| `prevOriginX/Y`, `prevWidth/Height` | Position and size of the previous pass buffer |
|
||||
|
||||
`GetPrevTile(x, y)` safely reads the previous pass at any absolute world coordinate, returning `0` (AIR) for out-of-bounds or when no previous pass exists.
|
||||
|
||||
---
|
||||
|
||||
## Graph (`WorldGraph.h`)
|
||||
|
||||
`Graph` owns a set of nodes and the connections between them.
|
||||
|
||||
```
|
||||
NodeID AddNode(unique_ptr<Node>) — add a node, returns its ID
|
||||
bool Connect(from, to, inputSlot) — wire an output to an input
|
||||
Value Evaluate(outputNode, ctx) — evaluate for one cell
|
||||
bool IsValid(outputNode) — check all inputs are wired
|
||||
```
|
||||
|
||||
**Cycle detection** runs at `Connect()` time. Any connection that would form a cycle is rejected and returns `false`.
|
||||
|
||||
**Evaluation** is recursive and on-demand. Starting from the output node, the graph walks upstream, evaluating each dependency before the node that depends on it. Unconnected inputs receive a zero value of their declared type.
|
||||
|
||||
---
|
||||
|
||||
## Nodes (`WorldGraphNode.h`)
|
||||
|
||||
All nodes derive from the abstract `Node` base class and implement:
|
||||
- `GetOutputType()` — the type this node produces
|
||||
- `GetInputTypes()` — the types of each input slot
|
||||
- `Evaluate(ctx, inputs)` — computes and returns the output value
|
||||
|
||||
### Source Nodes (no inputs)
|
||||
|
||||
| Node | Output | Description |
|
||||
|---|---|---|
|
||||
| `ConstantNode` | Float | A fixed float value |
|
||||
| `IDNode` | Int | A fixed tile ID integer |
|
||||
| `PositionXNode` | Int | World X coordinate of the current cell |
|
||||
| `PositionYNode` | Int | World Y coordinate of the current cell |
|
||||
|
||||
### Math Nodes
|
||||
|
||||
| Node | Inputs | Output | Description |
|
||||
|---|---|---|---|
|
||||
| `AddNode` | Float, Float | Float | a + b |
|
||||
| `SubtractNode` | Float, Float | Float | a − b |
|
||||
| `MultiplyNode` | Float, Float | Float | a × b |
|
||||
| `DivideNode` | Float, Float | Float | a / b (0 on divide-by-zero) |
|
||||
| `ModuloNode` | Float, Float | Float | fmod(a, b) |
|
||||
| `SinNode` | Float | Float | sin(a) in radians |
|
||||
| `CosNode` | Float | Float | cos(a) in radians |
|
||||
|
||||
### Comparison Nodes
|
||||
|
||||
All produce `Bool`. Inputs are Float.
|
||||
|
||||
`LessNode`, `GreaterNode`, `LessEqualNode`, `GreaterEqualNode`, `EqualNode`
|
||||
|
||||
> **Note:** `EqualNode` compares floats directly — use only with integer-valued inputs.
|
||||
|
||||
### Boolean Logic Nodes
|
||||
|
||||
| Node | Inputs | Output |
|
||||
|---|---|---|
|
||||
| `AndNode` | Bool, Bool | Bool |
|
||||
| `OrNode` | Bool, Bool | Bool |
|
||||
| `NotNode` | Bool | Bool |
|
||||
|
||||
### Control Flow
|
||||
|
||||
`BranchNode` — selects between two values based on a condition:
|
||||
|
||||
```
|
||||
inputs[0] Bool — condition
|
||||
inputs[1] Float — value if true
|
||||
inputs[2] Float — value if false
|
||||
```
|
||||
|
||||
The concrete type of the chosen branch is passed through, so an `IDNode` connected to the true/false slot will produce an `Int` even though `GetOutputType()` reports `Float`.
|
||||
|
||||
### Previous-Pass Query Nodes
|
||||
|
||||
These nodes read from the previous generation pass via `EvalContext`. They have **no inputs** — their parameters are baked into the node at construction time. The chunk generator uses these baked offsets to pre-compute how much border padding the previous pass must produce.
|
||||
|
||||
| Node | Output | Description |
|
||||
|---|---|---|
|
||||
| `QueryTileNode(offsetX, offsetY, expectedID)` | Bool | `true` if the tile at `(worldX+offsetX, worldY+offsetY)` equals `expectedID` |
|
||||
| `QueryRangeNode(minX, minY, maxX, maxY, tileID)` | Int | Count of tiles matching `tileID` within the relative rectangle |
|
||||
| `QueryDistanceNode(tileID, maxDistance)` | Int | Chebyshev distance to the nearest tile matching `tileID`; returns `maxDistance + 1` if none found. The current cell is excluded. |
|
||||
|
||||
---
|
||||
|
||||
## Multi-Pass Chunk Generation (`WorldGraphChunk.h`)
|
||||
|
||||
Complex terrain is built in **ordered passes**, where each pass can read the output of the previous one. For example: pass 1 places stone, pass 2 adds ores based on nearby stone.
|
||||
|
||||
### `GenerationPass`
|
||||
|
||||
A thin struct pairing a `Graph` with the `NodeID` of its output node.
|
||||
|
||||
### Zero = "No Change"
|
||||
|
||||
When a pass outputs `0` for a cell, it means **keep the previous pass's value** (or AIR if there was no previous pass). This lets later passes act as selective overrides rather than full rewrites.
|
||||
|
||||
### `GenerateRegion`
|
||||
|
||||
Generates a single rectangular region using one graph. Walks every cell, builds an `EvalContext` pointing at `prevPassData`, evaluates the graph, and writes the result.
|
||||
|
||||
### `GenerateChunk`
|
||||
|
||||
Runs a full sequence of passes over a chunk, automatically computing the padding each pass needs.
|
||||
|
||||
```
|
||||
Algorithm:
|
||||
1. The last pass generates exactly [chunkOriginX, chunkOriginY, W × H].
|
||||
2. Working backwards from the last pass, ComputeRequiredPadding inspects
|
||||
every QueryTile / QueryRange / QueryDistance node in each pass's graph
|
||||
to determine how much the previous pass's output region must expand.
|
||||
This expansion accumulates from last pass to first.
|
||||
3. Passes execute in forward order. Each feeds its TileGrid output to the
|
||||
next pass as prevPassData.
|
||||
4. The final TileGrid covers the original chunk region only.
|
||||
```
|
||||
|
||||
### `PaddingBounds`
|
||||
|
||||
Records how many extra tiles are needed beyond the output region in each direction (`negX`, `posX`, `negY`, `posY`). Computed automatically by `ComputeRequiredPadding`, which walks the graph and unions the baked offsets from all query nodes.
|
||||
|
||||
---
|
||||
|
||||
## Serialization (`WorldGraphSerializer.h`)
|
||||
|
||||
`GraphSerializer` converts a `Graph` to/from JSON (nlohmann/json).
|
||||
|
||||
```cpp
|
||||
nlohmann::json j = GraphSerializer::ToJson(graph);
|
||||
optional<Graph> g = GraphSerializer::FromJson(j);
|
||||
bool ok = GraphSerializer::Save(graph, "path/to/file.json");
|
||||
optional<Graph> g2 = GraphSerializer::Load("path/to/file.json");
|
||||
```
|
||||
|
||||
JSON structure:
|
||||
```json
|
||||
{
|
||||
"nextId": 5,
|
||||
"nodes": [
|
||||
{ "id": 1, "type": "Constant", "value": 0.5 },
|
||||
{ "id": 2, "type": "TileID", "tileId": 3 },
|
||||
{ "id": 3, "type": "Branch" }
|
||||
],
|
||||
"connections": [
|
||||
{ "from": 1, "to": 3, "slot": 0 },
|
||||
{ "from": 2, "to": 3, "slot": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Only `Constant` and `TileID` nodes carry extra fields. All query nodes (`QueryTile`, `QueryRange`, `QueryDistance`) serialize their baked offset/ID parameters.
|
||||
|
||||
---
|
||||
|
||||
## Minimal Usage Example
|
||||
|
||||
```cpp
|
||||
using namespace WorldGraph;
|
||||
|
||||
// Build a graph that places tile 2 when Y < -10, otherwise tile 1
|
||||
Graph g;
|
||||
auto posY = g.AddNode(std::make_unique<PositionYNode>());
|
||||
auto thresh = g.AddNode(std::make_unique<ConstantNode>(-10.0f));
|
||||
auto cond = g.AddNode(std::make_unique<LessNode>());
|
||||
auto tileA = g.AddNode(std::make_unique<IDNode>(2)); // underground tile
|
||||
auto tileB = g.AddNode(std::make_unique<IDNode>(1)); // surface tile
|
||||
auto branch = g.AddNode(std::make_unique<BranchNode>());
|
||||
|
||||
g.Connect(posY, cond, 0); // posY → Less.a
|
||||
g.Connect(thresh, cond, 1); // -10 → Less.b
|
||||
g.Connect(cond, branch, 0); // bool → Branch.condition
|
||||
g.Connect(tileA, branch, 1); // tile2 → Branch.true
|
||||
g.Connect(tileB, branch, 2); // tile1 → Branch.false
|
||||
|
||||
// Generate a 64×64 chunk at world position (0, -32)
|
||||
GenerationPass pass { g, branch };
|
||||
TileGrid chunk = GenerateChunk({ pass }, 0, -32, 64, 64, /*seed=*/12345);
|
||||
```
|
||||
65
tools/node-editor/CMakeLists.txt
Normal file
65
tools/node-editor/CMakeLists.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
find_package(OpenGL REQUIRED)
|
||||
include(FetchContent)
|
||||
|
||||
# ── ImGui static library ──────────────────────────────────────────────────────
|
||||
|
||||
add_library(imgui_lib STATIC
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imgui_lib PUBLIC
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
|
||||
target_link_libraries(imgui_lib PUBLIC glfw OpenGL::GL)
|
||||
target_compile_definitions(imgui_lib PUBLIC IMGUI_DEFINE_MATH_OPERATORS)
|
||||
|
||||
# ── imnodes static library ────────────────────────────────────────────────────
|
||||
|
||||
add_library(imnodes_lib STATIC
|
||||
${imnodes_SOURCE_DIR}/imnodes.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imnodes_lib PUBLIC
|
||||
${imnodes_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(imnodes_lib PUBLIC imgui_lib)
|
||||
|
||||
# ── ImGuiFileDialog static library ───────────────────────────────────────────
|
||||
# Declared in root CMakeLists.txt; populated here so we build only the sources
|
||||
# we need without running ImGuiFileDialog's own CMakeLists.txt.
|
||||
|
||||
FetchContent_GetProperties(ImGuiFileDialog)
|
||||
if(NOT imguifiledialog_POPULATED)
|
||||
FetchContent_Populate(ImGuiFileDialog)
|
||||
endif()
|
||||
|
||||
add_library(imgui_file_dialog_lib STATIC
|
||||
${imguifiledialog_SOURCE_DIR}/ImGuiFileDialog.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imgui_file_dialog_lib PUBLIC
|
||||
${imguifiledialog_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(imgui_file_dialog_lib PUBLIC imgui_lib)
|
||||
|
||||
# ── Node editor executable ────────────────────────────────────────────────────
|
||||
|
||||
add_executable(node-editor
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(node-editor PRIVATE
|
||||
factory-hole-core
|
||||
imgui_lib
|
||||
imnodes_lib
|
||||
imgui_file_dialog_lib
|
||||
)
|
||||
143
tools/node-editor/README.md
Normal file
143
tools/node-editor/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# WorldGraph Node Editor
|
||||
|
||||
A standalone visual node editor for the WorldGraph procedural generation system, built with Dear ImGui and imnodes. Every node displays a live 64×64 preview of its output, ShaderGraph-style.
|
||||
|
||||
## Building & Running
|
||||
|
||||
```bash
|
||||
make world-editor
|
||||
```
|
||||
|
||||
This configures, builds, and launches the editor in one step.
|
||||
|
||||
---
|
||||
|
||||
## Interface
|
||||
|
||||
### Layout
|
||||
|
||||
| Area | Description |
|
||||
|---|---|
|
||||
| Left sidebar | Preview settings, World Output status, help |
|
||||
| Main canvas | Node graph — pan with middle-mouse, zoom with scroll |
|
||||
|
||||
### Adding Nodes
|
||||
|
||||
Right-click anywhere on the empty canvas to open the node menu, grouped by category. The node spawns at the click position.
|
||||
|
||||
### Connecting Nodes
|
||||
|
||||
Drag from an **output pin** (right side of a node) to an **input pin** (left side). Dragging in either direction works. Connecting to an already-wired input replaces the existing connection. Cycles are rejected automatically.
|
||||
|
||||
### Disconnecting
|
||||
|
||||
Click an existing link to select it, then press **Delete**. Or drag a new connection onto the same input slot to replace it.
|
||||
|
||||
### Deleting Nodes
|
||||
|
||||
Select one or more nodes and press **Delete**. The **World Output** node cannot be deleted.
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Ctrl+S` | Save |
|
||||
| `Ctrl+O` | Open |
|
||||
| `Delete` | Delete selected nodes or links |
|
||||
|
||||
---
|
||||
|
||||
## World Output Node
|
||||
|
||||
The **World Output** node (blue title bar) is the fixed endpoint of the graph. It is always present and cannot be moved off-canvas or deleted.
|
||||
|
||||
Each input slot represents one **generation pass**. Passes execute in order — pass 1 can read the tile output of pass 0 via `QueryTile`, `QueryRange`, and `QueryDistance` nodes.
|
||||
|
||||
- **Connect a pass:** drag any node's output pin into a `Pass N` slot.
|
||||
- **Add a pass slot:** click `+ Pass` inside the node.
|
||||
- **Remove the last pass slot:** click `- Pass`.
|
||||
|
||||
The World Output preview shows the result of running `GenerateChunk()` with all connected passes over a 64×64 tile region starting at the preview origin. Each pixel equals one world tile.
|
||||
|
||||
---
|
||||
|
||||
## Per-Node Previews
|
||||
|
||||
Every node in the graph renders its own 64×64 preview by evaluating the subgraph rooted at that node across a grid of world coordinates.
|
||||
|
||||
| Output type | Preview color |
|
||||
|---|---|
|
||||
| `Float` | Grayscale, clamped to `[0, 1]` |
|
||||
| `Bool` | White (true) / black (false) |
|
||||
| `Int` (tile ID) | Hashed to a distinct hue; tile 0 (AIR) = near-black |
|
||||
|
||||
**Scale** in the sidebar controls how many world units one pixel represents in per-node previews. This lets you zoom in on high-frequency noise or zoom out to see large-scale structure.
|
||||
|
||||
> Query nodes (`QueryTile`, `QueryRange`, `QueryDistance`) always preview as blank in the per-node view because no previous-pass data is available at that stage. They work correctly in the World Output preview when used in a later pass.
|
||||
|
||||
---
|
||||
|
||||
## Preview Settings (Sidebar)
|
||||
|
||||
| Setting | Effect |
|
||||
|---|---|
|
||||
| **Origin X / Y** | World-space top-left corner of all previews |
|
||||
| **Scale** | World units per pixel (per-node previews only; World Output is always 1 tile/pixel) |
|
||||
| **Seed** | World seed passed to every `EvalContext` |
|
||||
|
||||
---
|
||||
|
||||
## Node Types
|
||||
|
||||
### Source
|
||||
| Node | Output | Description |
|
||||
|---|---|---|
|
||||
| `Constant` | Float | Fixed float value (drag to edit) |
|
||||
| `TileID` | Int | Fixed tile ID integer (drag to edit) |
|
||||
| `PositionX` | Int | World X coordinate of the current cell |
|
||||
| `PositionY` | Int | World Y coordinate of the current cell |
|
||||
|
||||
### Math
|
||||
`Add`, `Subtract`, `Multiply`, `Divide`, `Modulo`, `Sin`, `Cos`
|
||||
|
||||
### Compare
|
||||
`Less`, `Greater`, `LessEqual`, `GreaterEqual`, `Equal` — all output `Bool`
|
||||
|
||||
### Logic
|
||||
`And`, `Or`, `Not`
|
||||
|
||||
### Control
|
||||
| Node | Inputs | Description |
|
||||
|---|---|---|
|
||||
| `Branch` | condition (Bool), true, false | Passes through whichever branch the condition selects |
|
||||
|
||||
### Query (previous pass)
|
||||
These nodes read from the previous generation pass. They have no input pins — their parameters are edited inline by dragging.
|
||||
|
||||
| Node | Output | Parameters |
|
||||
|---|---|---|
|
||||
| `QueryTile` | Bool | `offsetX`, `offsetY`, `expectedID` — true if prev-pass tile at offset equals ID |
|
||||
| `QueryRange` | Int | `minX..maxX`, `minY..maxY`, `tileID` — count of matching tiles in rectangle |
|
||||
| `QueryDistance` | Int | `tileID`, `maxDistance` — Chebyshev distance to nearest matching tile |
|
||||
|
||||
---
|
||||
|
||||
## File Format
|
||||
|
||||
Graphs are saved as `.wge` JSON files containing both the graph data and editor layout:
|
||||
|
||||
```json
|
||||
{
|
||||
"graph": { ... },
|
||||
"editor": {
|
||||
"nodePositions": { "1": [x, y], "__worldOutput": [x, y] },
|
||||
"worldOutputPasses": [3, 7],
|
||||
"seed": 0,
|
||||
"previewOriginX": 0,
|
||||
"previewOriginY": 0,
|
||||
"previewScale": 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`worldOutputPasses` is an array of graph node IDs, one per pass slot (`0` = empty slot).
|
||||
1185
tools/node-editor/main.cpp
Normal file
1185
tools/node-editor/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user