layer strength
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -531,7 +531,6 @@ qrc_*.cpp
|
||||
ui_*.h
|
||||
*.qmlc
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
*.qm
|
||||
*.prl
|
||||
@@ -941,7 +940,6 @@ CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
|
||||
26
Makefile
Normal file
26
Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
BUILD_DIR := build
|
||||
BUILD_TYPE ?= Debug
|
||||
|
||||
.PHONY: all configure build run test clean world-editor
|
||||
|
||||
all: build
|
||||
|
||||
configure:
|
||||
@cmake -S . -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE)
|
||||
|
||||
build: configure
|
||||
@cmake --build $(BUILD_DIR) --target factory-hole-app -j$$(nproc)
|
||||
|
||||
run: build
|
||||
@$(BUILD_DIR)/factory-hole-app
|
||||
|
||||
test: configure
|
||||
@cmake --build $(BUILD_DIR) --target factory-hole-tests -j$$(nproc)
|
||||
@cd $(BUILD_DIR) && ctest --output-on-failure
|
||||
|
||||
world-editor: configure
|
||||
@cmake --build $(BUILD_DIR) --target node-editor -j$$(nproc)
|
||||
@$(BUILD_DIR)/tools/node-editor/node-editor
|
||||
|
||||
clean:
|
||||
@rm -rf $(BUILD_DIR)
|
||||
@@ -1,49 +1,141 @@
|
||||
{
|
||||
"editor": {
|
||||
"nodePositions": {
|
||||
"1": [
|
||||
-1011.0,
|
||||
-214.0
|
||||
],
|
||||
"10": [
|
||||
-601.0,
|
||||
-250.0
|
||||
],
|
||||
"11": [
|
||||
-337.0,
|
||||
-265.0
|
||||
],
|
||||
"12": [
|
||||
-351.2725830078125,
|
||||
240.48638916015625
|
||||
-504.2725830078125,
|
||||
-131.51361083984375
|
||||
],
|
||||
"2": [
|
||||
-740.0,
|
||||
-231.0
|
||||
"13": [
|
||||
-195.0,
|
||||
-26.0
|
||||
],
|
||||
"3": [
|
||||
-885.0,
|
||||
-153.0
|
||||
"15": [
|
||||
-184.0,
|
||||
-228.0
|
||||
],
|
||||
"16": [
|
||||
-329.0,
|
||||
-47.0
|
||||
],
|
||||
"17": [
|
||||
-20.0,
|
||||
-149.0
|
||||
],
|
||||
"18": [
|
||||
131.0,
|
||||
-75.0
|
||||
],
|
||||
"19": [
|
||||
-9.0,
|
||||
76.0
|
||||
],
|
||||
"21": [
|
||||
-380.255615234375,
|
||||
459.421142578125
|
||||
],
|
||||
"23": [
|
||||
-154.0,
|
||||
248.0
|
||||
],
|
||||
"24": [
|
||||
175.0,
|
||||
331.0
|
||||
],
|
||||
"25": [
|
||||
-523.0,
|
||||
220.0
|
||||
],
|
||||
"26": [
|
||||
-655.7763671875,
|
||||
236.2835693359375
|
||||
],
|
||||
"27": [
|
||||
-817.7763671875,
|
||||
418.2835693359375
|
||||
],
|
||||
"28": [
|
||||
-816.0,
|
||||
249.0
|
||||
],
|
||||
"29": [
|
||||
-357.0,
|
||||
248.0
|
||||
],
|
||||
"30": [
|
||||
-516.0,
|
||||
408.0
|
||||
],
|
||||
"31": [
|
||||
-7.0,
|
||||
389.0
|
||||
],
|
||||
"32": [
|
||||
-588.0,
|
||||
597.0
|
||||
],
|
||||
"33": [
|
||||
-151.0,
|
||||
497.0
|
||||
],
|
||||
"34": [
|
||||
-1580.6732177734375,
|
||||
-252.9154052734375
|
||||
],
|
||||
"37": [
|
||||
-938.6732788085938,
|
||||
-13.9154052734375
|
||||
],
|
||||
"39": [
|
||||
-1082.0,
|
||||
58.0
|
||||
],
|
||||
"4": [
|
||||
-472.0,
|
||||
-118.0
|
||||
-261.0,
|
||||
-633.0
|
||||
],
|
||||
"40": [
|
||||
-1297.0,
|
||||
179.0
|
||||
],
|
||||
"41": [
|
||||
-858.0,
|
||||
-289.0
|
||||
],
|
||||
"42": [
|
||||
-1519.0,
|
||||
-63.0
|
||||
],
|
||||
"43": [
|
||||
-1431.0,
|
||||
-267.0
|
||||
],
|
||||
"44": [
|
||||
-1261.0,
|
||||
-226.0
|
||||
],
|
||||
"45": [
|
||||
-666.0,
|
||||
-158.0
|
||||
],
|
||||
"46": [
|
||||
-799.0,
|
||||
33.0
|
||||
],
|
||||
"5": [
|
||||
-144.0,
|
||||
-283.0
|
||||
-122.0,
|
||||
-513.0
|
||||
],
|
||||
"6": [
|
||||
-341.0,
|
||||
-61.0
|
||||
-275.0,
|
||||
-428.0
|
||||
],
|
||||
"7": [
|
||||
-23.0,
|
||||
-218.0
|
||||
-1.0,
|
||||
-448.0
|
||||
],
|
||||
"9": [
|
||||
136.0,
|
||||
-300.0
|
||||
158.0,
|
||||
-530.0
|
||||
],
|
||||
"__worldOutput": [
|
||||
400.0,
|
||||
@@ -52,65 +144,18 @@
|
||||
},
|
||||
"previewOriginX": 0,
|
||||
"previewOriginY": 0,
|
||||
"previewScale": 0.4000000059604645,
|
||||
"previewScale": 0.5,
|
||||
"seed": 0,
|
||||
"tileRegistry": [
|
||||
{
|
||||
"color": [
|
||||
0.11764705926179886,
|
||||
0.11764705926179886,
|
||||
0.11764705926179886
|
||||
],
|
||||
"id": 0,
|
||||
"name": "Empty"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.6958129405975342,
|
||||
0.6926098465919495,
|
||||
0.7119565010070801
|
||||
],
|
||||
"id": 1,
|
||||
"name": "Stone"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.05567699670791626,
|
||||
0.7880434989929199,
|
||||
0.09547946602106094
|
||||
],
|
||||
"id": 2,
|
||||
"name": "Dirt"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.2771739363670349,
|
||||
0.06326796859502792,
|
||||
0.0
|
||||
],
|
||||
"id": 3,
|
||||
"name": "Tree"
|
||||
}
|
||||
],
|
||||
"worldOutputPasses": [
|
||||
9,
|
||||
0
|
||||
18,
|
||||
24
|
||||
]
|
||||
},
|
||||
"graph": {
|
||||
"connections": [
|
||||
{
|
||||
"from": 1,
|
||||
"slot": 0,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 3,
|
||||
"slot": 1,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 11,
|
||||
"from": 4,
|
||||
"slot": 0,
|
||||
"to": 5
|
||||
},
|
||||
@@ -130,36 +175,143 @@
|
||||
"to": 9
|
||||
},
|
||||
{
|
||||
"from": 2,
|
||||
"from": 12,
|
||||
"slot": 0,
|
||||
"to": 10
|
||||
"to": 15
|
||||
},
|
||||
{
|
||||
"from": 10,
|
||||
"slot": 0,
|
||||
"to": 11
|
||||
},
|
||||
{
|
||||
"from": 4,
|
||||
"from": 16,
|
||||
"slot": 1,
|
||||
"to": 11
|
||||
"to": 15
|
||||
},
|
||||
{
|
||||
"from": 15,
|
||||
"slot": 0,
|
||||
"to": 17
|
||||
},
|
||||
{
|
||||
"from": 13,
|
||||
"slot": 1,
|
||||
"to": 17
|
||||
},
|
||||
{
|
||||
"from": 17,
|
||||
"slot": 0,
|
||||
"to": 18
|
||||
},
|
||||
{
|
||||
"from": 19,
|
||||
"slot": 1,
|
||||
"to": 18
|
||||
},
|
||||
{
|
||||
"from": 31,
|
||||
"slot": 0,
|
||||
"to": 24
|
||||
},
|
||||
{
|
||||
"from": 23,
|
||||
"slot": 1,
|
||||
"to": 24
|
||||
},
|
||||
{
|
||||
"from": 26,
|
||||
"slot": 0,
|
||||
"to": 25
|
||||
},
|
||||
{
|
||||
"from": 28,
|
||||
"slot": 0,
|
||||
"to": 26
|
||||
},
|
||||
{
|
||||
"from": 27,
|
||||
"slot": 1,
|
||||
"to": 26
|
||||
},
|
||||
{
|
||||
"from": 25,
|
||||
"slot": 0,
|
||||
"to": 29
|
||||
},
|
||||
{
|
||||
"from": 30,
|
||||
"slot": 1,
|
||||
"to": 29
|
||||
},
|
||||
{
|
||||
"from": 29,
|
||||
"slot": 0,
|
||||
"to": 31
|
||||
},
|
||||
{
|
||||
"from": 33,
|
||||
"slot": 1,
|
||||
"to": 31
|
||||
},
|
||||
{
|
||||
"from": 21,
|
||||
"slot": 0,
|
||||
"to": 33
|
||||
},
|
||||
{
|
||||
"from": 32,
|
||||
"slot": 1,
|
||||
"to": 33
|
||||
},
|
||||
{
|
||||
"from": 39,
|
||||
"slot": 0,
|
||||
"to": 37
|
||||
},
|
||||
{
|
||||
"from": 44,
|
||||
"slot": 0,
|
||||
"to": 39
|
||||
},
|
||||
{
|
||||
"from": 40,
|
||||
"slot": 1,
|
||||
"to": 39
|
||||
},
|
||||
{
|
||||
"from": 43,
|
||||
"slot": 0,
|
||||
"to": 41
|
||||
},
|
||||
{
|
||||
"from": 42,
|
||||
"slot": 1,
|
||||
"to": 41
|
||||
},
|
||||
{
|
||||
"from": 34,
|
||||
"slot": 0,
|
||||
"to": 43
|
||||
},
|
||||
{
|
||||
"from": 43,
|
||||
"slot": 0,
|
||||
"to": 44
|
||||
},
|
||||
{
|
||||
"from": 42,
|
||||
"slot": 1,
|
||||
"to": 44
|
||||
},
|
||||
{
|
||||
"from": 37,
|
||||
"slot": 0,
|
||||
"to": 45
|
||||
},
|
||||
{
|
||||
"from": 46,
|
||||
"slot": 1,
|
||||
"to": 45
|
||||
}
|
||||
],
|
||||
"nextId": 13,
|
||||
"nextId": 47,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PositionY"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Multiply"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "Constant",
|
||||
"value": 0.019999999552965164
|
||||
},
|
||||
{
|
||||
"frequency": 0.07880000025033951,
|
||||
"id": 4,
|
||||
@@ -172,7 +324,7 @@
|
||||
{
|
||||
"id": 6,
|
||||
"type": "Constant",
|
||||
"value": 0.1599999964237213
|
||||
"value": 0.17000000178813934
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
@@ -184,21 +336,143 @@
|
||||
"type": "IntBranch"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "Max"
|
||||
"id": 12,
|
||||
"maxX": 0,
|
||||
"maxY": 2,
|
||||
"minX": 0,
|
||||
"minY": 0,
|
||||
"tileId": 0,
|
||||
"type": "QueryRange"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"expectedID": 1,
|
||||
"id": 13,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"type": "QueryTile"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "GreaterEqual"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "Constant",
|
||||
"value": 1.0
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "And"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "IntBranch"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"tileId": 2,
|
||||
"type": "TileID"
|
||||
},
|
||||
{
|
||||
"expectedID": 2,
|
||||
"id": 21,
|
||||
"offsetX": 0,
|
||||
"offsetY": -1,
|
||||
"type": "QueryTile"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"tileId": 3,
|
||||
"type": "TileID"
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "IntBranch"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "Floor"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "Add"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "Constant",
|
||||
"value": 0.05999999865889549
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "Random"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "GreaterEqual"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "Constant",
|
||||
"value": 1.0
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "And"
|
||||
},
|
||||
{
|
||||
"expectedID": 0,
|
||||
"id": 32,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"type": "QueryTile"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"type": "And"
|
||||
},
|
||||
{
|
||||
"frequency": 0.09839999675750732,
|
||||
"id": 34,
|
||||
"type": "CellularNoise"
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"type": "Ceil"
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"type": "Add"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"type": "Constant",
|
||||
"value": -0.5
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "Multiply"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"maxX": 1,
|
||||
"maxY": 1,
|
||||
"minX": -1,
|
||||
"minY": -1,
|
||||
"tileId": 1,
|
||||
"type": "QueryRange"
|
||||
"id": 42,
|
||||
"type": "Constant",
|
||||
"value": 500.0
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"type": "Negate"
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "Pow"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "Greater"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"type": "Constant",
|
||||
"value": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"editor": {
|
||||
"nodePositions": {
|
||||
"1": [
|
||||
-70.0,
|
||||
-259.0
|
||||
],
|
||||
"13": [
|
||||
-220.0,
|
||||
24.0
|
||||
@@ -17,6 +13,10 @@
|
||||
-223.0,
|
||||
206.0
|
||||
],
|
||||
"17": [
|
||||
-190.0,
|
||||
-250.0
|
||||
],
|
||||
"2": [
|
||||
112.0,
|
||||
-289.0
|
||||
@@ -34,26 +34,6 @@
|
||||
"previewOriginY": 0,
|
||||
"previewScale": 0.4000000059604645,
|
||||
"seed": 0,
|
||||
"tileRegistry": [
|
||||
{
|
||||
"color": [
|
||||
0.11764705926179886,
|
||||
0.11764705926179886,
|
||||
0.11764705926179886
|
||||
],
|
||||
"id": 0,
|
||||
"name": "Empty"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.7348794341087341,
|
||||
0.730800986289978,
|
||||
0.7554347515106201
|
||||
],
|
||||
"id": 1,
|
||||
"name": "Stone"
|
||||
}
|
||||
],
|
||||
"worldOutputPasses": [
|
||||
2
|
||||
]
|
||||
@@ -66,8 +46,8 @@
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 1,
|
||||
"slot": 1,
|
||||
"from": 17,
|
||||
"slot": 2,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
@@ -86,13 +66,8 @@
|
||||
"to": 15
|
||||
}
|
||||
],
|
||||
"nextId": 17,
|
||||
"nextId": 18,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"tileId": 1,
|
||||
"type": "TileID"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "IntBranch"
|
||||
@@ -114,6 +89,11 @@
|
||||
"id": 16,
|
||||
"type": "Constant",
|
||||
"value": 0.3199999928474426
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"tileId": 1,
|
||||
"type": "TileID"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
40
data/tiles.json
Normal file
40
data/tiles.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"tiles": [
|
||||
{
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"id": 0,
|
||||
"name": "Empty"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.532608687877655,
|
||||
0.532608687877655,
|
||||
0.532608687877655
|
||||
],
|
||||
"id": 1,
|
||||
"name": "Stone"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.2549019753932953,
|
||||
0.8470588326454163,
|
||||
0.43529412150382996
|
||||
],
|
||||
"id": 2,
|
||||
"name": "Grass"
|
||||
},
|
||||
{
|
||||
"color": [
|
||||
0.0,
|
||||
0.32065218687057495,
|
||||
0.01742672547698021
|
||||
],
|
||||
"id": 3,
|
||||
"name": "Tree"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
imgui.ini
13
imgui.ini
@@ -1,10 +1,10 @@
|
||||
[Window][Node Canvas]
|
||||
Pos=200,42
|
||||
Size=1720,1039
|
||||
Size=1080,678
|
||||
|
||||
[Window][Settings]
|
||||
Pos=0,42
|
||||
Size=200,1039
|
||||
Size=200,678
|
||||
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
@@ -18,6 +18,10 @@ Size=600,400
|
||||
Pos=60,60
|
||||
Size=600,400
|
||||
|
||||
[Window][Save Tile Registry##SaveRegistryDlg]
|
||||
Pos=60,60
|
||||
Size=600,400
|
||||
|
||||
[Table][0x4ED31530,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
@@ -86,7 +90,12 @@ Column 0 Sort=0v
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x493FDC9E,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[NodeEditor][Session]
|
||||
LastFiles=/home/connor/repos/factory-hole-core/data/WorldGraphs/stone.json;/home/connor/repos/factory-hole-core/data/WorldGraphs/dirt.json
|
||||
ActiveTab=1
|
||||
SharedRegistry=/home/connor/repos/factory-hole-core/data/tiles.json
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ public:
|
||||
QueryRangeNode(int32_t mnX, int32_t mnY, int32_t mxX, int32_t mxY, int32_t id)
|
||||
: minX(mnX), minY(mnY), maxX(mxX), maxY(mxY), tileID(id) {}
|
||||
|
||||
Type GetOutputType() const override { return Type::Int; }
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return {}; }
|
||||
std::string GetName() const override { return "QueryRange"; }
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
|
||||
@@ -304,7 +304,7 @@ public:
|
||||
for (int32_t dx = minX; dx <= maxX; ++dx)
|
||||
if (ctx.GetPrevTile(ctx.worldX + dx, ctx.worldY + dy) == tileID)
|
||||
++count;
|
||||
return Value::MakeInt(count);
|
||||
return Value::MakeFloat(static_cast<float>(count));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -381,6 +381,16 @@ public:
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeFloat(ctx.worldY); };
|
||||
};
|
||||
|
||||
/// Outputs the current layer's blending strength, set by the world compositor.
|
||||
/// Returns 1.0 when running outside a layered world (normal graph evaluation).
|
||||
class LayerStrengthNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return {}; }
|
||||
std::string GetName() const override { return "LayerStrength"; }
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeFloat(ctx.layerStrength); }
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Abs / Negate ────────────────────────────────
|
||||
|
||||
/// |a| (Float)
|
||||
@@ -501,6 +511,30 @@ public:
|
||||
|
||||
// ─────────────────────────────── Extended math nodes ─────────────────────────
|
||||
|
||||
/// floor(a) (Float)
|
||||
class FloorNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float }; }
|
||||
std::string GetName() const override { return "Floor"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
return Value::MakeFloat(std::floor(in[0].AsFloat()));
|
||||
}
|
||||
};
|
||||
|
||||
/// ceil(a) (Float)
|
||||
class CeilNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float }; }
|
||||
std::string GetName() const override { return "Ceil"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
return Value::MakeFloat(std::ceil(in[0].AsFloat()));
|
||||
}
|
||||
};
|
||||
|
||||
/// sqrt(a), clamped to 0 for negative inputs (Float)
|
||||
class SqrtNode : public Node {
|
||||
public:
|
||||
@@ -605,6 +639,28 @@ public:
|
||||
// Each noise node reads worldX/Y and seed from EvalContext; no graph inputs.
|
||||
// Output is a Float in [-1, 1].
|
||||
//
|
||||
// RandomNode is the exception: it produces spatially incoherent white noise
|
||||
// via a hash of (seed, worldX, worldY), output in [0, 1].
|
||||
|
||||
/// Spatially incoherent hash-based random value in [0, 1].
|
||||
/// Each (seed, worldX, worldY) triple produces an independent value — no
|
||||
/// smoothing, no spatial correlation. (Float)
|
||||
class RandomNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return {}; }
|
||||
std::string GetName() const override { return "Random"; }
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
|
||||
// Mix seed, x, y with Knuth multiplicative hashes then finalise.
|
||||
uint32_t h = static_cast<uint32_t>(ctx.seed)
|
||||
^ (static_cast<uint32_t>(ctx.worldX) * 2654435761u)
|
||||
^ (static_cast<uint32_t>(ctx.worldY) * 2246822519u);
|
||||
h ^= h >> 16;
|
||||
h *= 0x45d9f3bu;
|
||||
h ^= h >> 16;
|
||||
return Value::MakeFloat((h & 0xFFFFFFu) / static_cast<float>(0x1000000u));
|
||||
}
|
||||
};
|
||||
// A new FastNoiseLite object is constructed per evaluation so that the seed
|
||||
// from the context is applied correctly across every cell.
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ struct EvalContext {
|
||||
int32_t prevWidth { 0 };
|
||||
int32_t prevHeight { 0 };
|
||||
|
||||
// ── Layer blending strength ────────────────────────────────────────────
|
||||
// Set by the world layer compositor before calling Evaluate().
|
||||
// 1.0 = this layer is fully active; 0.0 = this layer has no influence.
|
||||
float layerStrength { 1.0f };
|
||||
|
||||
/// Query the previous pass at an absolute world position.
|
||||
/// Returns 0 (AIR / empty) when no previous pass or position is out of bounds.
|
||||
inline int32_t GetPrevTile(int32_t x, int32_t y) const noexcept;
|
||||
|
||||
@@ -31,9 +31,12 @@ std::unique_ptr<Node> GraphSerializer::CreateNode(const std::string& type,
|
||||
if (type == "IntBranch") return std::make_unique<IntBranchNode>();
|
||||
if (type == "PositionX") return std::make_unique<PositionXNode>();
|
||||
if (type == "PositionY") return std::make_unique<PositionYNode>();
|
||||
if (type == "LayerStrength") return std::make_unique<LayerStrengthNode>();
|
||||
if (type == "Sin") return std::make_unique<SinNode>();
|
||||
if (type == "Cos") return std::make_unique<CosNode>();
|
||||
if (type == "Modulo") return std::make_unique<ModuloNode>();
|
||||
if (type == "Floor") return std::make_unique<FloorNode>();
|
||||
if (type == "Ceil") return std::make_unique<CeilNode>();
|
||||
if (type == "Sqrt") return std::make_unique<SqrtNode>();
|
||||
if (type == "Pow") return std::make_unique<PowNode>();
|
||||
if (type == "Square") return std::make_unique<SquareNode>();
|
||||
@@ -51,7 +54,8 @@ std::unique_ptr<Node> GraphSerializer::CreateNode(const std::string& type,
|
||||
j.value("max1", 1.0f));
|
||||
}
|
||||
|
||||
// Noise nodes (frequency baked in)
|
||||
// Noise nodes
|
||||
if (type == "Random") return std::make_unique<RandomNode>();
|
||||
if (type == "PerlinNoise") return std::make_unique<PerlinNoiseNode>(j.value("frequency", 0.01f));
|
||||
if (type == "SimplexNoise") return std::make_unique<SimplexNoiseNode>(j.value("frequency", 0.01f));
|
||||
if (type == "CellularNoise") return std::make_unique<CellularNoiseNode>(j.value("frequency",0.01f));
|
||||
|
||||
@@ -162,6 +162,7 @@ static ImU32 PinColorHovered(Type t)
|
||||
|
||||
static constexpr int PREVIEW_SIZE = 64;
|
||||
static constexpr float PREVIEW_SCALE = 1.5f; // world units per pixel
|
||||
static constexpr float FINAL_PREVIEW_SCALE = 4.0f; // for World Output node
|
||||
|
||||
static GLuint MakePreviewTexture()
|
||||
{
|
||||
@@ -205,6 +206,7 @@ static const std::vector<NodeMenuItem> NODE_MENU = {
|
||||
{ "TileID", "Source", [] { return std::make_unique<IDNode>(1); } },
|
||||
{ "PositionX", "Source", [] { return std::make_unique<PositionXNode>(); } },
|
||||
{ "PositionY", "Source", [] { return std::make_unique<PositionYNode>(); } },
|
||||
{ "LayerStrength", "Source", [] { return std::make_unique<LayerStrengthNode>(); } },
|
||||
// ── Math ────────────────────────────────────────────────────────────────
|
||||
{ "Add", "Math", [] { return std::make_unique<AddNode>(); } },
|
||||
{ "Subtract", "Math", [] { return std::make_unique<SubtractNode>(); } },
|
||||
@@ -213,6 +215,8 @@ static const std::vector<NodeMenuItem> NODE_MENU = {
|
||||
{ "Modulo", "Math", [] { return std::make_unique<ModuloNode>(); } },
|
||||
{ "Sin", "Math", [] { return std::make_unique<SinNode>(); } },
|
||||
{ "Cos", "Math", [] { return std::make_unique<CosNode>(); } },
|
||||
{ "Floor", "Math", [] { return std::make_unique<FloorNode>(); } },
|
||||
{ "Ceil", "Math", [] { return std::make_unique<CeilNode>(); } },
|
||||
{ "Sqrt", "Math", [] { return std::make_unique<SqrtNode>(); } },
|
||||
{ "Pow", "Math", [] { return std::make_unique<PowNode>(); } },
|
||||
{ "Square", "Math", [] { return std::make_unique<SquareNode>(); } },
|
||||
@@ -245,6 +249,7 @@ static const std::vector<NodeMenuItem> NODE_MENU = {
|
||||
{ "QueryRange", "Query", [] { return std::make_unique<QueryRangeNode>(-1, -1, 1, 1, 1); } },
|
||||
{ "QueryDistance", "Query", [] { return std::make_unique<QueryDistanceNode>(1, 4); } },
|
||||
// ── Noise ───────────────────────────────────────────────────────────────
|
||||
{ "Random", "Noise", [] { return std::make_unique<RandomNode>(); } },
|
||||
{ "PerlinNoise", "Noise", [] { return std::make_unique<PerlinNoiseNode>(0.01f); } },
|
||||
{ "SimplexNoise", "Noise", [] { return std::make_unique<SimplexNoiseNode>(0.01f); } },
|
||||
{ "CellularNoise", "Noise", [] { return std::make_unique<CellularNoiseNode>(0.01f); } },
|
||||
@@ -260,7 +265,6 @@ struct GraphTab {
|
||||
|
||||
Graph graph;
|
||||
std::unordered_map<Graph::NodeID, VisualNode> visualNodes;
|
||||
std::vector<TileEntry> tileRegistry;
|
||||
|
||||
std::vector<Graph::NodeID> worldOutputPasses;
|
||||
GLuint worldOutputTex { 0 };
|
||||
@@ -276,7 +280,6 @@ struct GraphTab {
|
||||
|
||||
void Init()
|
||||
{
|
||||
tileRegistry = { TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}} };
|
||||
worldOutputPasses = { Graph::INVALID_ID };
|
||||
worldOutputTex = MakePreviewTexture();
|
||||
imnodesCtx = ImNodes::EditorContextCreate();
|
||||
@@ -317,6 +320,10 @@ public:
|
||||
void Render()
|
||||
{
|
||||
// Restore last session written by the ini settings handler.
|
||||
if (!pendingLoadRegistry.empty()) {
|
||||
LoadSharedRegistry(pendingLoadRegistry);
|
||||
pendingLoadRegistry.clear();
|
||||
}
|
||||
if (!pendingLoadFiles.empty()) {
|
||||
for (const auto& f : pendingLoadFiles) {
|
||||
auto& cur = Active();
|
||||
@@ -378,9 +385,16 @@ private:
|
||||
int activeTab { 0 };
|
||||
int requestedTab { -1 }; // set to trigger programmatic tab switch (one frame)
|
||||
|
||||
// Shared tile registry — one across all tabs / graph files
|
||||
std::vector<TileEntry> sharedRegistry {
|
||||
TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}}
|
||||
};
|
||||
std::string sharedRegistryPath;
|
||||
|
||||
// Pending data from ini settings handler
|
||||
std::vector<std::string> pendingLoadFiles;
|
||||
int pendingActiveTab { -1 };
|
||||
std::string pendingLoadRegistry;
|
||||
|
||||
GraphTab& Active() { return tabs[activeTab]; }
|
||||
|
||||
@@ -449,12 +463,12 @@ private:
|
||||
for (int px = 0; px < PREVIEW_SIZE; ++px) {
|
||||
EvalContext ctx;
|
||||
ctx.worldX = tab.previewOriginX + static_cast<int>(px * tab.previewScale);
|
||||
ctx.worldY = tab.previewOriginY + static_cast<int>(py * tab.previewScale);
|
||||
ctx.worldY = tab.previewOriginY + static_cast<int>((PREVIEW_SIZE - 1 - py) * tab.previewScale);
|
||||
ctx.seed = tab.previewSeed;
|
||||
|
||||
Value v = tab.graph.Evaluate(id, ctx);
|
||||
int base = (py * PREVIEW_SIZE + px) * 4;
|
||||
ValueToRGBA(v, &tab.tileRegistry,
|
||||
ValueToRGBA(v, &sharedRegistry,
|
||||
pixels[base], pixels[base+1], pixels[base+2], pixels[base+3]);
|
||||
}
|
||||
}
|
||||
@@ -485,10 +499,10 @@ private:
|
||||
|
||||
for (int py = 0; py < PREVIEW_SIZE; ++py) {
|
||||
for (int px = 0; px < PREVIEW_SIZE; ++px) {
|
||||
int32_t tileID = chunk.Get(tab.previewOriginX + px, tab.previewOriginY + py);
|
||||
int32_t tileID = chunk.Get(tab.previewOriginX + px, tab.previewOriginY + (PREVIEW_SIZE - 1 - py));
|
||||
int base = (py * PREVIEW_SIZE + px) * 4;
|
||||
Value v = Value::MakeInt(tileID);
|
||||
ValueToRGBA(v, &tab.tileRegistry,
|
||||
ValueToRGBA(v, &sharedRegistry,
|
||||
pixels[base], pixels[base+1], pixels[base+2], pixels[base+3]);
|
||||
}
|
||||
}
|
||||
@@ -546,10 +560,27 @@ private:
|
||||
ImGui::Spacing();
|
||||
ImGui::SeparatorText("Tile IDs");
|
||||
|
||||
// Registry file path (read-only display)
|
||||
{
|
||||
const char* rpath = sharedRegistryPath.empty() ? "(unsaved)" : sharedRegistryPath.c_str();
|
||||
ImGui::TextDisabled("%s", rpath);
|
||||
}
|
||||
if (ImGui::SmallButton("Open##reg")) OpenRegistryDialog();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Save##reg"))
|
||||
{
|
||||
if (sharedRegistryPath.empty()) SaveRegistryDialog();
|
||||
else SaveSharedRegistry(sharedRegistryPath);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Save As##reg")) SaveRegistryDialog();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
int toRemove = -1;
|
||||
for (int i = 0; i < static_cast<int>(tab.tileRegistry.size()); ++i) {
|
||||
for (int i = 0; i < static_cast<int>(sharedRegistry.size()); ++i) {
|
||||
ImGui::PushID(i);
|
||||
TileEntry& entry = tab.tileRegistry[i];
|
||||
TileEntry& entry = sharedRegistry[i];
|
||||
|
||||
// Color button — opens a popup picker.
|
||||
char cpopup[32];
|
||||
@@ -593,11 +624,11 @@ private:
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (toRemove >= 0)
|
||||
tab.tileRegistry.erase(tab.tileRegistry.begin() + toRemove);
|
||||
sharedRegistry.erase(sharedRegistry.begin() + toRemove);
|
||||
|
||||
if (ImGui::SmallButton("+ Add Tile")) {
|
||||
int32_t nextId = tab.tileRegistry.empty() ? 1
|
||||
: tab.tileRegistry.back().id + 1;
|
||||
int32_t nextId = sharedRegistry.empty() ? 1
|
||||
: sharedRegistry.back().id + 1;
|
||||
TileEntry entry;
|
||||
entry.id = nextId;
|
||||
entry.name = "Tile";
|
||||
@@ -608,7 +639,7 @@ private:
|
||||
entry.color[0] = hr / 255.0f;
|
||||
entry.color[1] = hg / 255.0f;
|
||||
entry.color[2] = hb / 255.0f;
|
||||
tab.tileRegistry.push_back(entry);
|
||||
sharedRegistry.push_back(entry);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -913,7 +944,7 @@ private:
|
||||
// Generated chunk preview — always 1 tile per pixel.
|
||||
ImGui::Image(
|
||||
static_cast<ImTextureID>(static_cast<uint64_t>(tab.worldOutputTex)),
|
||||
ImVec2(PREVIEW_SIZE * PREVIEW_SCALE, PREVIEW_SIZE * PREVIEW_SCALE));
|
||||
ImVec2(PREVIEW_SIZE * FINAL_PREVIEW_SCALE, PREVIEW_SIZE * FINAL_PREVIEW_SCALE));
|
||||
|
||||
ImNodes::EndNode();
|
||||
|
||||
@@ -986,38 +1017,46 @@ private:
|
||||
return ImGui::DragFloat("##val", &n->value, 0.01f);
|
||||
}
|
||||
|
||||
static bool DrawIDParams(NodeEditorApp& app, Node* node)
|
||||
static bool DrawIDDropdown(const char* label, NodeEditorApp& app, int& currentID)
|
||||
{
|
||||
auto* n = static_cast<IDNode*>(node);
|
||||
auto& reg = app.Active().tileRegistry;
|
||||
bool changed = false;
|
||||
if (reg.empty()) {
|
||||
changed = ImGui::DragInt("##id", &n->tileID, 1, 0, 9999);
|
||||
} else {
|
||||
auto& reg = app.sharedRegistry;
|
||||
int selIdx = -1;
|
||||
bool changed = false;
|
||||
for (int i = 0; i < static_cast<int>(reg.size()); ++i)
|
||||
if (reg[i].id == n->tileID) { selIdx = i; break; }
|
||||
if (reg[i].id == currentID) { selIdx = i; break; }
|
||||
const char* preview = selIdx >= 0 ? reg[selIdx].name.c_str() : "???";
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::BeginCombo("##tile", preview)) {
|
||||
if (ImGui::BeginCombo(label, preview)) {
|
||||
for (int i = 0; i < static_cast<int>(reg.size()); ++i) {
|
||||
bool sel = (i == selIdx);
|
||||
char label[80];
|
||||
snprintf(label, sizeof(label), "%s (%d)",
|
||||
reg[i].name.c_str(), reg[i].id);
|
||||
if (ImGui::Selectable(label, sel)) {
|
||||
n->tileID = reg[i].id;
|
||||
currentID = reg[i].id;
|
||||
changed = true;
|
||||
}
|
||||
if (sel) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool DrawIDParams(NodeEditorApp& app, Node* node)
|
||||
{
|
||||
auto* n = static_cast<IDNode*>(node);
|
||||
auto& reg = app.sharedRegistry;
|
||||
bool changed = false;
|
||||
if (reg.empty()) {
|
||||
changed = ImGui::DragInt("##id", &n->tileID, 1, 0, 9999);
|
||||
} else {
|
||||
changed |= DrawIDDropdown("##id", app, n->tileID);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool DrawQueryTileParams(NodeEditorApp& /*app*/, Node* node)
|
||||
static bool DrawQueryTileParams(NodeEditorApp& app, Node* node)
|
||||
{
|
||||
auto* n = static_cast<QueryTileNode*>(node);
|
||||
bool changed = false;
|
||||
@@ -1028,33 +1067,35 @@ private:
|
||||
changed |= ImGui::DragInt("##oy", &n->offsetY, 1);
|
||||
// TODO: it would be best if this is a dropdown of tile ids that the user can choose from
|
||||
ImGui::Text("Tile ID");
|
||||
changed |= ImGui::DragInt("##eid", &n->expectedID, 1, 0, 1024);
|
||||
changed |= DrawIDDropdown("##eid", app, n->expectedID);
|
||||
ImGui::PopItemWidth();
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool DrawQueryRangeParams(NodeEditorApp& /*app*/, Node* node)
|
||||
static bool DrawQueryRangeParams(NodeEditorApp& app, Node* node)
|
||||
{
|
||||
auto* n = static_cast<QueryRangeNode*>(node);
|
||||
bool changed = false;
|
||||
ImGui::PushItemWidth(70);
|
||||
ImGui::Text("min x -> max x");
|
||||
changed |= ImGui::DragInt("##mnx", &n->minX, 1);
|
||||
ImGui::SameLine();
|
||||
changed |= ImGui::DragInt("##mxx", &n->maxX, 1);
|
||||
ImGui::Text("min y -> max y");
|
||||
changed |= ImGui::DragInt("##mny", &n->minY, 1);
|
||||
ImGui::SameLine();
|
||||
changed |= ImGui::DragInt("##mxy", &n->maxY, 1);
|
||||
changed |= ImGui::DragInt("##rtid", &n->tileID, 1, 0, 1024);
|
||||
changed |= DrawIDDropdown("##rtid", app, n->tileID);
|
||||
ImGui::PopItemWidth();
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool DrawQueryDistanceParams(NodeEditorApp& /*app*/, Node* node)
|
||||
static bool DrawQueryDistanceParams(NodeEditorApp& app, Node* node)
|
||||
{
|
||||
auto* n = static_cast<QueryDistanceNode*>(node);
|
||||
bool changed = false;
|
||||
ImGui::PushItemWidth(70);
|
||||
changed |= ImGui::DragInt("##dtid", &n->tileID, 1, 0, 1024);
|
||||
changed |= DrawIDDropdown("##dtid", app, n->tileID);
|
||||
changed |= ImGui::DragInt("##md", &n->maxDistance, 1, 1, 32);
|
||||
ImGui::PopItemWidth();
|
||||
return changed;
|
||||
@@ -1126,6 +1167,75 @@ private:
|
||||
|
||||
// ── Serialization ─────────────────────────────────────────────────────────
|
||||
|
||||
// ── Shared tile registry I/O ──────────────────────────────────────────────
|
||||
|
||||
void SaveSharedRegistry(const std::string& path)
|
||||
{
|
||||
nlohmann::json j;
|
||||
auto& tiles = j["tiles"] = nlohmann::json::array();
|
||||
for (auto& t : sharedRegistry)
|
||||
tiles.push_back({ {"id", t.id}, {"name", t.name},
|
||||
{"color", { t.color[0], t.color[1], t.color[2] }} });
|
||||
std::ofstream f(path);
|
||||
if (f) { f << j.dump(2); sharedRegistryPath = path; }
|
||||
else fprintf(stderr, "Failed to write registry %s\n", path.c_str());
|
||||
}
|
||||
|
||||
void LoadSharedRegistry(const std::string& path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f) { fprintf(stderr, "Cannot open registry %s\n", path.c_str()); return; }
|
||||
nlohmann::json j;
|
||||
try { j = nlohmann::json::parse(f); }
|
||||
catch (...) { fprintf(stderr, "JSON parse error in registry %s\n", path.c_str()); return; }
|
||||
|
||||
if (!j.contains("tiles") || !j["tiles"].is_array()) return;
|
||||
|
||||
sharedRegistry.clear();
|
||||
for (auto& t : j["tiles"]) {
|
||||
TileEntry entry;
|
||||
entry.id = t.value("id", 0);
|
||||
entry.name = t.value("name", std::string("Tile"));
|
||||
if (t.contains("color") && t["color"].size() == 3) {
|
||||
entry.color[0] = t["color"][0].get<float>();
|
||||
entry.color[1] = t["color"][1].get<float>();
|
||||
entry.color[2] = t["color"][2].get<float>();
|
||||
}
|
||||
sharedRegistry.push_back(entry);
|
||||
}
|
||||
// Always ensure ID 0 sentinel is present.
|
||||
bool hasEmpty = std::any_of(sharedRegistry.begin(), sharedRegistry.end(),
|
||||
[](const TileEntry& e) { return e.id == 0; });
|
||||
if (!hasEmpty)
|
||||
sharedRegistry.insert(sharedRegistry.begin(),
|
||||
TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}});
|
||||
|
||||
sharedRegistryPath = path;
|
||||
MarkAllDirty();
|
||||
}
|
||||
|
||||
void OpenRegistryDialog()
|
||||
{
|
||||
IGFD::FileDialogConfig cfg;
|
||||
cfg.path = sharedRegistryPath.empty() ? "."
|
||||
: std::filesystem::path(sharedRegistryPath).parent_path().string();
|
||||
ImGuiFileDialog::Instance()->OpenDialog("OpenRegistryDlg", "Open Tile Registry", ".json", cfg);
|
||||
}
|
||||
|
||||
void SaveRegistryDialog()
|
||||
{
|
||||
IGFD::FileDialogConfig cfg;
|
||||
if (!sharedRegistryPath.empty()) {
|
||||
std::filesystem::path p(sharedRegistryPath);
|
||||
cfg.path = p.parent_path().string();
|
||||
cfg.fileName = p.filename().string();
|
||||
} else {
|
||||
cfg.path = ".";
|
||||
cfg.fileName = "tiles.json";
|
||||
}
|
||||
ImGuiFileDialog::Instance()->OpenDialog("SaveRegistryDlg", "Save Tile Registry", ".json", cfg);
|
||||
}
|
||||
|
||||
void Save(const std::string& path)
|
||||
{
|
||||
auto& tab = Active();
|
||||
@@ -1156,12 +1266,6 @@ private:
|
||||
passes.push_back(id);
|
||||
root["editor"]["worldOutputPasses"] = passes;
|
||||
|
||||
nlohmann::json tiles = nlohmann::json::array();
|
||||
for (auto& t : tab.tileRegistry)
|
||||
tiles.push_back({ {"id", t.id}, {"name", t.name},
|
||||
{"color", { t.color[0], t.color[1], t.color[2] }} });
|
||||
root["editor"]["tileRegistry"] = tiles;
|
||||
|
||||
std::ofstream f(path);
|
||||
if (f) { f << root.dump(2); tab.currentFile = path; }
|
||||
else fprintf(stderr, "Failed to write %s\n", path.c_str());
|
||||
@@ -1188,7 +1292,6 @@ private:
|
||||
tab.worldOutputDirty = true;
|
||||
tab.pendingWorldOutputPos = true;
|
||||
tab.currentFile = "";
|
||||
tab.tileRegistry = { TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}} };
|
||||
|
||||
tab.graph = std::move(*newGraph);
|
||||
|
||||
@@ -1210,26 +1313,8 @@ private:
|
||||
tab.previewOriginY = ed.value("previewOriginY", 0);
|
||||
tab.previewScale = ed.value("previewScale", 1.0f);
|
||||
|
||||
if (ed.contains("tileRegistry")) {
|
||||
tab.tileRegistry.clear();
|
||||
for (auto& t : ed["tileRegistry"]) {
|
||||
TileEntry entry;
|
||||
entry.id = t.value("id", 0);
|
||||
entry.name = t.value("name", std::string("Tile"));
|
||||
if (t.contains("color") && t["color"].size() == 3) {
|
||||
entry.color[0] = t["color"][0].get<float>();
|
||||
entry.color[1] = t["color"][1].get<float>();
|
||||
entry.color[2] = t["color"][2].get<float>();
|
||||
}
|
||||
tab.tileRegistry.push_back(entry);
|
||||
}
|
||||
// Always guarantee the Empty (ID 0) sentinel is present.
|
||||
bool hasEmpty = std::any_of(tab.tileRegistry.begin(), tab.tileRegistry.end(),
|
||||
[](const TileEntry& e) { return e.id == 0; });
|
||||
if (!hasEmpty)
|
||||
tab.tileRegistry.insert(tab.tileRegistry.begin(),
|
||||
TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}});
|
||||
}
|
||||
// Note: "tileRegistry" embedded in old .wge files is intentionally
|
||||
// ignored — use the shared tiles.json registry instead.
|
||||
|
||||
if (ed.contains("worldOutputPasses")) {
|
||||
tab.worldOutputPasses.clear();
|
||||
@@ -1320,6 +1405,20 @@ public:
|
||||
Save(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
|
||||
if (ImGuiFileDialog::Instance()->Display("OpenRegistryDlg",
|
||||
ImGuiWindowFlags_NoCollapse, dlgSize)) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk())
|
||||
LoadSharedRegistry(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
|
||||
if (ImGuiFileDialog::Instance()->Display("SaveRegistryDlg",
|
||||
ImGuiWindowFlags_NoCollapse, dlgSize)) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk())
|
||||
SaveSharedRegistry(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||
ImGuiFileDialog::Instance()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleKeyboardShortcuts()
|
||||
@@ -1358,6 +1457,8 @@ public:
|
||||
}
|
||||
if (strncmp(line, "ActiveTab=", 10) == 0)
|
||||
app->pendingActiveTab = std::atoi(line + 10);
|
||||
if (strncmp(line, "SharedRegistry=", 15) == 0)
|
||||
app->pendingLoadRegistry = line + 15;
|
||||
};
|
||||
|
||||
// Called when ImGui writes imgui.ini (periodically and on shutdown).
|
||||
@@ -1370,12 +1471,14 @@ public:
|
||||
files += tab.currentFile;
|
||||
}
|
||||
}
|
||||
if (!files.empty()) {
|
||||
out_buf->appendf("[NodeEditor][Session]\n");
|
||||
if (!files.empty()) {
|
||||
out_buf->appendf("LastFiles=%s\n", files.c_str());
|
||||
out_buf->appendf("ActiveTab=%d\n", app->activeTab);
|
||||
out_buf->appendf("\n");
|
||||
}
|
||||
if (!app->sharedRegistryPath.empty())
|
||||
out_buf->appendf("SharedRegistry=%s\n", app->sharedRegistryPath.c_str());
|
||||
out_buf->appendf("\n");
|
||||
};
|
||||
|
||||
ImGui::AddSettingsHandler(&h);
|
||||
|
||||
Reference in New Issue
Block a user