layer strength
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -531,7 +531,6 @@ qrc_*.cpp
|
|||||||
ui_*.h
|
ui_*.h
|
||||||
*.qmlc
|
*.qmlc
|
||||||
*.jsc
|
*.jsc
|
||||||
Makefile*
|
|
||||||
*build-*
|
*build-*
|
||||||
*.qm
|
*.qm
|
||||||
*.prl
|
*.prl
|
||||||
@@ -941,7 +940,6 @@ CMakeCache.txt
|
|||||||
CMakeFiles
|
CMakeFiles
|
||||||
CMakeScripts
|
CMakeScripts
|
||||||
Testing
|
Testing
|
||||||
Makefile
|
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
install_manifest.txt
|
install_manifest.txt
|
||||||
compile_commands.json
|
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": {
|
"editor": {
|
||||||
"nodePositions": {
|
"nodePositions": {
|
||||||
"1": [
|
|
||||||
-1011.0,
|
|
||||||
-214.0
|
|
||||||
],
|
|
||||||
"10": [
|
|
||||||
-601.0,
|
|
||||||
-250.0
|
|
||||||
],
|
|
||||||
"11": [
|
|
||||||
-337.0,
|
|
||||||
-265.0
|
|
||||||
],
|
|
||||||
"12": [
|
"12": [
|
||||||
-351.2725830078125,
|
-504.2725830078125,
|
||||||
240.48638916015625
|
-131.51361083984375
|
||||||
],
|
],
|
||||||
"2": [
|
"13": [
|
||||||
-740.0,
|
-195.0,
|
||||||
-231.0
|
-26.0
|
||||||
],
|
],
|
||||||
"3": [
|
"15": [
|
||||||
-885.0,
|
-184.0,
|
||||||
-153.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": [
|
"4": [
|
||||||
-472.0,
|
-261.0,
|
||||||
-118.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": [
|
"5": [
|
||||||
-144.0,
|
-122.0,
|
||||||
-283.0
|
-513.0
|
||||||
],
|
],
|
||||||
"6": [
|
"6": [
|
||||||
-341.0,
|
-275.0,
|
||||||
-61.0
|
-428.0
|
||||||
],
|
],
|
||||||
"7": [
|
"7": [
|
||||||
-23.0,
|
-1.0,
|
||||||
-218.0
|
-448.0
|
||||||
],
|
],
|
||||||
"9": [
|
"9": [
|
||||||
136.0,
|
158.0,
|
||||||
-300.0
|
-530.0
|
||||||
],
|
],
|
||||||
"__worldOutput": [
|
"__worldOutput": [
|
||||||
400.0,
|
400.0,
|
||||||
@@ -52,65 +144,18 @@
|
|||||||
},
|
},
|
||||||
"previewOriginX": 0,
|
"previewOriginX": 0,
|
||||||
"previewOriginY": 0,
|
"previewOriginY": 0,
|
||||||
"previewScale": 0.4000000059604645,
|
"previewScale": 0.5,
|
||||||
"seed": 0,
|
"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": [
|
"worldOutputPasses": [
|
||||||
9,
|
9,
|
||||||
0
|
18,
|
||||||
|
24
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"graph": {
|
"graph": {
|
||||||
"connections": [
|
"connections": [
|
||||||
{
|
{
|
||||||
"from": 1,
|
"from": 4,
|
||||||
"slot": 0,
|
|
||||||
"to": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": 3,
|
|
||||||
"slot": 1,
|
|
||||||
"to": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": 11,
|
|
||||||
"slot": 0,
|
"slot": 0,
|
||||||
"to": 5
|
"to": 5
|
||||||
},
|
},
|
||||||
@@ -130,36 +175,143 @@
|
|||||||
"to": 9
|
"to": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": 2,
|
"from": 12,
|
||||||
"slot": 0,
|
"slot": 0,
|
||||||
"to": 10
|
"to": 15
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": 10,
|
"from": 16,
|
||||||
"slot": 0,
|
|
||||||
"to": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": 4,
|
|
||||||
"slot": 1,
|
"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": [
|
"nodes": [
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "PositionY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "Multiply"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "Constant",
|
|
||||||
"value": 0.019999999552965164
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"frequency": 0.07880000025033951,
|
"frequency": 0.07880000025033951,
|
||||||
"id": 4,
|
"id": 4,
|
||||||
@@ -172,7 +324,7 @@
|
|||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"type": "Constant",
|
"type": "Constant",
|
||||||
"value": 0.1599999964237213
|
"value": 0.17000000178813934
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
@@ -184,21 +336,143 @@
|
|||||||
"type": "IntBranch"
|
"type": "IntBranch"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 10,
|
"id": 12,
|
||||||
"type": "Max"
|
"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"
|
"type": "Multiply"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 12,
|
"id": 42,
|
||||||
"maxX": 1,
|
"type": "Constant",
|
||||||
"maxY": 1,
|
"value": 500.0
|
||||||
"minX": -1,
|
},
|
||||||
"minY": -1,
|
{
|
||||||
"tileId": 1,
|
"id": 43,
|
||||||
"type": "QueryRange"
|
"type": "Negate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"type": "Pow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"type": "Greater"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"type": "Constant",
|
||||||
|
"value": 0.0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor": {
|
"editor": {
|
||||||
"nodePositions": {
|
"nodePositions": {
|
||||||
"1": [
|
|
||||||
-70.0,
|
|
||||||
-259.0
|
|
||||||
],
|
|
||||||
"13": [
|
"13": [
|
||||||
-220.0,
|
-220.0,
|
||||||
24.0
|
24.0
|
||||||
@@ -17,6 +13,10 @@
|
|||||||
-223.0,
|
-223.0,
|
||||||
206.0
|
206.0
|
||||||
],
|
],
|
||||||
|
"17": [
|
||||||
|
-190.0,
|
||||||
|
-250.0
|
||||||
|
],
|
||||||
"2": [
|
"2": [
|
||||||
112.0,
|
112.0,
|
||||||
-289.0
|
-289.0
|
||||||
@@ -34,26 +34,6 @@
|
|||||||
"previewOriginY": 0,
|
"previewOriginY": 0,
|
||||||
"previewScale": 0.4000000059604645,
|
"previewScale": 0.4000000059604645,
|
||||||
"seed": 0,
|
"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": [
|
"worldOutputPasses": [
|
||||||
2
|
2
|
||||||
]
|
]
|
||||||
@@ -66,8 +46,8 @@
|
|||||||
"to": 2
|
"to": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": 1,
|
"from": 17,
|
||||||
"slot": 1,
|
"slot": 2,
|
||||||
"to": 2
|
"to": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -86,13 +66,8 @@
|
|||||||
"to": 15
|
"to": 15
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nextId": 17,
|
"nextId": 18,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"tileId": 1,
|
|
||||||
"type": "TileID"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"type": "IntBranch"
|
"type": "IntBranch"
|
||||||
@@ -114,6 +89,11 @@
|
|||||||
"id": 16,
|
"id": 16,
|
||||||
"type": "Constant",
|
"type": "Constant",
|
||||||
"value": 0.3199999928474426
|
"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]
|
[Window][Node Canvas]
|
||||||
Pos=200,42
|
Pos=200,42
|
||||||
Size=1720,1039
|
Size=1080,678
|
||||||
|
|
||||||
[Window][Settings]
|
[Window][Settings]
|
||||||
Pos=0,42
|
Pos=0,42
|
||||||
Size=200,1039
|
Size=200,678
|
||||||
|
|
||||||
[Window][Debug##Default]
|
[Window][Debug##Default]
|
||||||
Pos=60,60
|
Pos=60,60
|
||||||
@@ -18,6 +18,10 @@ Size=600,400
|
|||||||
Pos=60,60
|
Pos=60,60
|
||||||
Size=600,400
|
Size=600,400
|
||||||
|
|
||||||
|
[Window][Save Tile Registry##SaveRegistryDlg]
|
||||||
|
Pos=60,60
|
||||||
|
Size=600,400
|
||||||
|
|
||||||
[Table][0x4ED31530,4]
|
[Table][0x4ED31530,4]
|
||||||
RefScale=13
|
RefScale=13
|
||||||
Column 0 Sort=0v
|
Column 0 Sort=0v
|
||||||
@@ -86,7 +90,12 @@ Column 0 Sort=0v
|
|||||||
RefScale=13
|
RefScale=13
|
||||||
Column 0 Sort=0v
|
Column 0 Sort=0v
|
||||||
|
|
||||||
|
[Table][0x493FDC9E,4]
|
||||||
|
RefScale=13
|
||||||
|
Column 0 Sort=0v
|
||||||
|
|
||||||
[NodeEditor][Session]
|
[NodeEditor][Session]
|
||||||
LastFiles=/home/connor/repos/factory-hole-core/data/WorldGraphs/stone.json;/home/connor/repos/factory-hole-core/data/WorldGraphs/dirt.json
|
LastFiles=/home/connor/repos/factory-hole-core/data/WorldGraphs/stone.json;/home/connor/repos/factory-hole-core/data/WorldGraphs/dirt.json
|
||||||
ActiveTab=1
|
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)
|
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) {}
|
: 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::vector<Type> GetInputTypes() const override { return {}; }
|
||||||
std::string GetName() const override { return "QueryRange"; }
|
std::string GetName() const override { return "QueryRange"; }
|
||||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
|
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override {
|
||||||
@@ -304,7 +304,7 @@ public:
|
|||||||
for (int32_t dx = minX; dx <= maxX; ++dx)
|
for (int32_t dx = minX; dx <= maxX; ++dx)
|
||||||
if (ctx.GetPrevTile(ctx.worldX + dx, ctx.worldY + dy) == tileID)
|
if (ctx.GetPrevTile(ctx.worldX + dx, ctx.worldY + dy) == tileID)
|
||||||
++count;
|
++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); };
|
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 ────────────────────────────────
|
// ─────────────────────────────── Abs / Negate ────────────────────────────────
|
||||||
|
|
||||||
/// |a| (Float)
|
/// |a| (Float)
|
||||||
@@ -501,6 +511,30 @@ public:
|
|||||||
|
|
||||||
// ─────────────────────────────── Extended math nodes ─────────────────────────
|
// ─────────────────────────────── 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)
|
/// sqrt(a), clamped to 0 for negative inputs (Float)
|
||||||
class SqrtNode : public Node {
|
class SqrtNode : public Node {
|
||||||
public:
|
public:
|
||||||
@@ -605,6 +639,28 @@ public:
|
|||||||
// Each noise node reads worldX/Y and seed from EvalContext; no graph inputs.
|
// Each noise node reads worldX/Y and seed from EvalContext; no graph inputs.
|
||||||
// Output is a Float in [-1, 1].
|
// 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
|
// A new FastNoiseLite object is constructed per evaluation so that the seed
|
||||||
// from the context is applied correctly across every cell.
|
// from the context is applied correctly across every cell.
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ struct EvalContext {
|
|||||||
int32_t prevWidth { 0 };
|
int32_t prevWidth { 0 };
|
||||||
int32_t prevHeight { 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.
|
/// Query the previous pass at an absolute world position.
|
||||||
/// Returns 0 (AIR / empty) when no previous pass or position is out of bounds.
|
/// 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;
|
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 == "IntBranch") return std::make_unique<IntBranchNode>();
|
||||||
if (type == "PositionX") return std::make_unique<PositionXNode>();
|
if (type == "PositionX") return std::make_unique<PositionXNode>();
|
||||||
if (type == "PositionY") return std::make_unique<PositionYNode>();
|
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 == "Sin") return std::make_unique<SinNode>();
|
||||||
if (type == "Cos") return std::make_unique<CosNode>();
|
if (type == "Cos") return std::make_unique<CosNode>();
|
||||||
if (type == "Modulo") return std::make_unique<ModuloNode>();
|
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 == "Sqrt") return std::make_unique<SqrtNode>();
|
||||||
if (type == "Pow") return std::make_unique<PowNode>();
|
if (type == "Pow") return std::make_unique<PowNode>();
|
||||||
if (type == "Square") return std::make_unique<SquareNode>();
|
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));
|
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 == "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 == "SimplexNoise") return std::make_unique<SimplexNoiseNode>(j.value("frequency", 0.01f));
|
||||||
if (type == "CellularNoise") return std::make_unique<CellularNoiseNode>(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 int PREVIEW_SIZE = 64;
|
||||||
static constexpr float PREVIEW_SCALE = 1.5f; // world units per pixel
|
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()
|
static GLuint MakePreviewTexture()
|
||||||
{
|
{
|
||||||
@@ -205,6 +206,7 @@ static const std::vector<NodeMenuItem> NODE_MENU = {
|
|||||||
{ "TileID", "Source", [] { return std::make_unique<IDNode>(1); } },
|
{ "TileID", "Source", [] { return std::make_unique<IDNode>(1); } },
|
||||||
{ "PositionX", "Source", [] { return std::make_unique<PositionXNode>(); } },
|
{ "PositionX", "Source", [] { return std::make_unique<PositionXNode>(); } },
|
||||||
{ "PositionY", "Source", [] { return std::make_unique<PositionYNode>(); } },
|
{ "PositionY", "Source", [] { return std::make_unique<PositionYNode>(); } },
|
||||||
|
{ "LayerStrength", "Source", [] { return std::make_unique<LayerStrengthNode>(); } },
|
||||||
// ── Math ────────────────────────────────────────────────────────────────
|
// ── Math ────────────────────────────────────────────────────────────────
|
||||||
{ "Add", "Math", [] { return std::make_unique<AddNode>(); } },
|
{ "Add", "Math", [] { return std::make_unique<AddNode>(); } },
|
||||||
{ "Subtract", "Math", [] { return std::make_unique<SubtractNode>(); } },
|
{ "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>(); } },
|
{ "Modulo", "Math", [] { return std::make_unique<ModuloNode>(); } },
|
||||||
{ "Sin", "Math", [] { return std::make_unique<SinNode>(); } },
|
{ "Sin", "Math", [] { return std::make_unique<SinNode>(); } },
|
||||||
{ "Cos", "Math", [] { return std::make_unique<CosNode>(); } },
|
{ "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>(); } },
|
{ "Sqrt", "Math", [] { return std::make_unique<SqrtNode>(); } },
|
||||||
{ "Pow", "Math", [] { return std::make_unique<PowNode>(); } },
|
{ "Pow", "Math", [] { return std::make_unique<PowNode>(); } },
|
||||||
{ "Square", "Math", [] { return std::make_unique<SquareNode>(); } },
|
{ "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); } },
|
{ "QueryRange", "Query", [] { return std::make_unique<QueryRangeNode>(-1, -1, 1, 1, 1); } },
|
||||||
{ "QueryDistance", "Query", [] { return std::make_unique<QueryDistanceNode>(1, 4); } },
|
{ "QueryDistance", "Query", [] { return std::make_unique<QueryDistanceNode>(1, 4); } },
|
||||||
// ── Noise ───────────────────────────────────────────────────────────────
|
// ── Noise ───────────────────────────────────────────────────────────────
|
||||||
|
{ "Random", "Noise", [] { return std::make_unique<RandomNode>(); } },
|
||||||
{ "PerlinNoise", "Noise", [] { return std::make_unique<PerlinNoiseNode>(0.01f); } },
|
{ "PerlinNoise", "Noise", [] { return std::make_unique<PerlinNoiseNode>(0.01f); } },
|
||||||
{ "SimplexNoise", "Noise", [] { return std::make_unique<SimplexNoiseNode>(0.01f); } },
|
{ "SimplexNoise", "Noise", [] { return std::make_unique<SimplexNoiseNode>(0.01f); } },
|
||||||
{ "CellularNoise", "Noise", [] { return std::make_unique<CellularNoiseNode>(0.01f); } },
|
{ "CellularNoise", "Noise", [] { return std::make_unique<CellularNoiseNode>(0.01f); } },
|
||||||
@@ -260,7 +265,6 @@ struct GraphTab {
|
|||||||
|
|
||||||
Graph graph;
|
Graph graph;
|
||||||
std::unordered_map<Graph::NodeID, VisualNode> visualNodes;
|
std::unordered_map<Graph::NodeID, VisualNode> visualNodes;
|
||||||
std::vector<TileEntry> tileRegistry;
|
|
||||||
|
|
||||||
std::vector<Graph::NodeID> worldOutputPasses;
|
std::vector<Graph::NodeID> worldOutputPasses;
|
||||||
GLuint worldOutputTex { 0 };
|
GLuint worldOutputTex { 0 };
|
||||||
@@ -276,7 +280,6 @@ struct GraphTab {
|
|||||||
|
|
||||||
void Init()
|
void Init()
|
||||||
{
|
{
|
||||||
tileRegistry = { TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}} };
|
|
||||||
worldOutputPasses = { Graph::INVALID_ID };
|
worldOutputPasses = { Graph::INVALID_ID };
|
||||||
worldOutputTex = MakePreviewTexture();
|
worldOutputTex = MakePreviewTexture();
|
||||||
imnodesCtx = ImNodes::EditorContextCreate();
|
imnodesCtx = ImNodes::EditorContextCreate();
|
||||||
@@ -317,6 +320,10 @@ public:
|
|||||||
void Render()
|
void Render()
|
||||||
{
|
{
|
||||||
// Restore last session written by the ini settings handler.
|
// Restore last session written by the ini settings handler.
|
||||||
|
if (!pendingLoadRegistry.empty()) {
|
||||||
|
LoadSharedRegistry(pendingLoadRegistry);
|
||||||
|
pendingLoadRegistry.clear();
|
||||||
|
}
|
||||||
if (!pendingLoadFiles.empty()) {
|
if (!pendingLoadFiles.empty()) {
|
||||||
for (const auto& f : pendingLoadFiles) {
|
for (const auto& f : pendingLoadFiles) {
|
||||||
auto& cur = Active();
|
auto& cur = Active();
|
||||||
@@ -378,9 +385,16 @@ private:
|
|||||||
int activeTab { 0 };
|
int activeTab { 0 };
|
||||||
int requestedTab { -1 }; // set to trigger programmatic tab switch (one frame)
|
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
|
// Pending data from ini settings handler
|
||||||
std::vector<std::string> pendingLoadFiles;
|
std::vector<std::string> pendingLoadFiles;
|
||||||
int pendingActiveTab { -1 };
|
int pendingActiveTab { -1 };
|
||||||
|
std::string pendingLoadRegistry;
|
||||||
|
|
||||||
GraphTab& Active() { return tabs[activeTab]; }
|
GraphTab& Active() { return tabs[activeTab]; }
|
||||||
|
|
||||||
@@ -449,12 +463,12 @@ private:
|
|||||||
for (int px = 0; px < PREVIEW_SIZE; ++px) {
|
for (int px = 0; px < PREVIEW_SIZE; ++px) {
|
||||||
EvalContext ctx;
|
EvalContext ctx;
|
||||||
ctx.worldX = tab.previewOriginX + static_cast<int>(px * tab.previewScale);
|
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;
|
ctx.seed = tab.previewSeed;
|
||||||
|
|
||||||
Value v = tab.graph.Evaluate(id, ctx);
|
Value v = tab.graph.Evaluate(id, ctx);
|
||||||
int base = (py * PREVIEW_SIZE + px) * 4;
|
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]);
|
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 py = 0; py < PREVIEW_SIZE; ++py) {
|
||||||
for (int px = 0; px < PREVIEW_SIZE; ++px) {
|
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;
|
int base = (py * PREVIEW_SIZE + px) * 4;
|
||||||
Value v = Value::MakeInt(tileID);
|
Value v = Value::MakeInt(tileID);
|
||||||
ValueToRGBA(v, &tab.tileRegistry,
|
ValueToRGBA(v, &sharedRegistry,
|
||||||
pixels[base], pixels[base+1], pixels[base+2], pixels[base+3]);
|
pixels[base], pixels[base+1], pixels[base+2], pixels[base+3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,10 +560,27 @@ private:
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::SeparatorText("Tile IDs");
|
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;
|
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);
|
ImGui::PushID(i);
|
||||||
TileEntry& entry = tab.tileRegistry[i];
|
TileEntry& entry = sharedRegistry[i];
|
||||||
|
|
||||||
// Color button — opens a popup picker.
|
// Color button — opens a popup picker.
|
||||||
char cpopup[32];
|
char cpopup[32];
|
||||||
@@ -593,11 +624,11 @@ private:
|
|||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
if (toRemove >= 0)
|
if (toRemove >= 0)
|
||||||
tab.tileRegistry.erase(tab.tileRegistry.begin() + toRemove);
|
sharedRegistry.erase(sharedRegistry.begin() + toRemove);
|
||||||
|
|
||||||
if (ImGui::SmallButton("+ Add Tile")) {
|
if (ImGui::SmallButton("+ Add Tile")) {
|
||||||
int32_t nextId = tab.tileRegistry.empty() ? 1
|
int32_t nextId = sharedRegistry.empty() ? 1
|
||||||
: tab.tileRegistry.back().id + 1;
|
: sharedRegistry.back().id + 1;
|
||||||
TileEntry entry;
|
TileEntry entry;
|
||||||
entry.id = nextId;
|
entry.id = nextId;
|
||||||
entry.name = "Tile";
|
entry.name = "Tile";
|
||||||
@@ -608,7 +639,7 @@ private:
|
|||||||
entry.color[0] = hr / 255.0f;
|
entry.color[0] = hr / 255.0f;
|
||||||
entry.color[1] = hg / 255.0f;
|
entry.color[1] = hg / 255.0f;
|
||||||
entry.color[2] = hb / 255.0f;
|
entry.color[2] = hb / 255.0f;
|
||||||
tab.tileRegistry.push_back(entry);
|
sharedRegistry.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
@@ -913,7 +944,7 @@ private:
|
|||||||
// Generated chunk preview — always 1 tile per pixel.
|
// Generated chunk preview — always 1 tile per pixel.
|
||||||
ImGui::Image(
|
ImGui::Image(
|
||||||
static_cast<ImTextureID>(static_cast<uint64_t>(tab.worldOutputTex)),
|
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();
|
ImNodes::EndNode();
|
||||||
|
|
||||||
@@ -986,38 +1017,46 @@ private:
|
|||||||
return ImGui::DragFloat("##val", &n->value, 0.01f);
|
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.sharedRegistry;
|
||||||
auto& reg = app.Active().tileRegistry;
|
|
||||||
bool changed = false;
|
|
||||||
if (reg.empty()) {
|
|
||||||
changed = ImGui::DragInt("##id", &n->tileID, 1, 0, 9999);
|
|
||||||
} else {
|
|
||||||
int selIdx = -1;
|
int selIdx = -1;
|
||||||
|
bool changed = false;
|
||||||
for (int i = 0; i < static_cast<int>(reg.size()); ++i)
|
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() : "???";
|
const char* preview = selIdx >= 0 ? reg[selIdx].name.c_str() : "???";
|
||||||
ImGui::SetNextItemWidth(80);
|
ImGui::SetNextItemWidth(80);
|
||||||
if (ImGui::BeginCombo("##tile", preview)) {
|
if (ImGui::BeginCombo(label, preview)) {
|
||||||
for (int i = 0; i < static_cast<int>(reg.size()); ++i) {
|
for (int i = 0; i < static_cast<int>(reg.size()); ++i) {
|
||||||
bool sel = (i == selIdx);
|
bool sel = (i == selIdx);
|
||||||
char label[80];
|
char label[80];
|
||||||
snprintf(label, sizeof(label), "%s (%d)",
|
snprintf(label, sizeof(label), "%s (%d)",
|
||||||
reg[i].name.c_str(), reg[i].id);
|
reg[i].name.c_str(), reg[i].id);
|
||||||
if (ImGui::Selectable(label, sel)) {
|
if (ImGui::Selectable(label, sel)) {
|
||||||
n->tileID = reg[i].id;
|
currentID = reg[i].id;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (sel) ImGui::SetItemDefaultFocus();
|
if (sel) ImGui::SetItemDefaultFocus();
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
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;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DrawQueryTileParams(NodeEditorApp& /*app*/, Node* node)
|
static bool DrawQueryTileParams(NodeEditorApp& app, Node* node)
|
||||||
{
|
{
|
||||||
auto* n = static_cast<QueryTileNode*>(node);
|
auto* n = static_cast<QueryTileNode*>(node);
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
@@ -1028,33 +1067,35 @@ private:
|
|||||||
changed |= ImGui::DragInt("##oy", &n->offsetY, 1);
|
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
|
// TODO: it would be best if this is a dropdown of tile ids that the user can choose from
|
||||||
ImGui::Text("Tile ID");
|
ImGui::Text("Tile ID");
|
||||||
changed |= ImGui::DragInt("##eid", &n->expectedID, 1, 0, 1024);
|
changed |= DrawIDDropdown("##eid", app, n->expectedID);
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DrawQueryRangeParams(NodeEditorApp& /*app*/, Node* node)
|
static bool DrawQueryRangeParams(NodeEditorApp& app, Node* node)
|
||||||
{
|
{
|
||||||
auto* n = static_cast<QueryRangeNode*>(node);
|
auto* n = static_cast<QueryRangeNode*>(node);
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
ImGui::PushItemWidth(70);
|
ImGui::PushItemWidth(70);
|
||||||
|
ImGui::Text("min x -> max x");
|
||||||
changed |= ImGui::DragInt("##mnx", &n->minX, 1);
|
changed |= ImGui::DragInt("##mnx", &n->minX, 1);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
changed |= ImGui::DragInt("##mxx", &n->maxX, 1);
|
changed |= ImGui::DragInt("##mxx", &n->maxX, 1);
|
||||||
|
ImGui::Text("min y -> max y");
|
||||||
changed |= ImGui::DragInt("##mny", &n->minY, 1);
|
changed |= ImGui::DragInt("##mny", &n->minY, 1);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
changed |= ImGui::DragInt("##mxy", &n->maxY, 1);
|
changed |= ImGui::DragInt("##mxy", &n->maxY, 1);
|
||||||
changed |= ImGui::DragInt("##rtid", &n->tileID, 1, 0, 1024);
|
changed |= DrawIDDropdown("##rtid", app, n->tileID);
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DrawQueryDistanceParams(NodeEditorApp& /*app*/, Node* node)
|
static bool DrawQueryDistanceParams(NodeEditorApp& app, Node* node)
|
||||||
{
|
{
|
||||||
auto* n = static_cast<QueryDistanceNode*>(node);
|
auto* n = static_cast<QueryDistanceNode*>(node);
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
ImGui::PushItemWidth(70);
|
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);
|
changed |= ImGui::DragInt("##md", &n->maxDistance, 1, 1, 32);
|
||||||
ImGui::PopItemWidth();
|
ImGui::PopItemWidth();
|
||||||
return changed;
|
return changed;
|
||||||
@@ -1126,6 +1167,75 @@ private:
|
|||||||
|
|
||||||
// ── Serialization ─────────────────────────────────────────────────────────
|
// ── 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)
|
void Save(const std::string& path)
|
||||||
{
|
{
|
||||||
auto& tab = Active();
|
auto& tab = Active();
|
||||||
@@ -1156,12 +1266,6 @@ private:
|
|||||||
passes.push_back(id);
|
passes.push_back(id);
|
||||||
root["editor"]["worldOutputPasses"] = passes;
|
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);
|
std::ofstream f(path);
|
||||||
if (f) { f << root.dump(2); tab.currentFile = path; }
|
if (f) { f << root.dump(2); tab.currentFile = path; }
|
||||||
else fprintf(stderr, "Failed to write %s\n", path.c_str());
|
else fprintf(stderr, "Failed to write %s\n", path.c_str());
|
||||||
@@ -1188,7 +1292,6 @@ private:
|
|||||||
tab.worldOutputDirty = true;
|
tab.worldOutputDirty = true;
|
||||||
tab.pendingWorldOutputPos = true;
|
tab.pendingWorldOutputPos = true;
|
||||||
tab.currentFile = "";
|
tab.currentFile = "";
|
||||||
tab.tileRegistry = { TileEntry{0, "Empty", {30.0f/255.0f, 30.0f/255.0f, 30.0f/255.0f}} };
|
|
||||||
|
|
||||||
tab.graph = std::move(*newGraph);
|
tab.graph = std::move(*newGraph);
|
||||||
|
|
||||||
@@ -1210,26 +1313,8 @@ private:
|
|||||||
tab.previewOriginY = ed.value("previewOriginY", 0);
|
tab.previewOriginY = ed.value("previewOriginY", 0);
|
||||||
tab.previewScale = ed.value("previewScale", 1.0f);
|
tab.previewScale = ed.value("previewScale", 1.0f);
|
||||||
|
|
||||||
if (ed.contains("tileRegistry")) {
|
// Note: "tileRegistry" embedded in old .wge files is intentionally
|
||||||
tab.tileRegistry.clear();
|
// ignored — use the shared tiles.json registry instead.
|
||||||
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}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ed.contains("worldOutputPasses")) {
|
if (ed.contains("worldOutputPasses")) {
|
||||||
tab.worldOutputPasses.clear();
|
tab.worldOutputPasses.clear();
|
||||||
@@ -1320,6 +1405,20 @@ public:
|
|||||||
Save(ImGuiFileDialog::Instance()->GetFilePathName());
|
Save(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||||
ImGuiFileDialog::Instance()->Close();
|
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()
|
void HandleKeyboardShortcuts()
|
||||||
@@ -1358,6 +1457,8 @@ public:
|
|||||||
}
|
}
|
||||||
if (strncmp(line, "ActiveTab=", 10) == 0)
|
if (strncmp(line, "ActiveTab=", 10) == 0)
|
||||||
app->pendingActiveTab = std::atoi(line + 10);
|
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).
|
// Called when ImGui writes imgui.ini (periodically and on shutdown).
|
||||||
@@ -1370,12 +1471,14 @@ public:
|
|||||||
files += tab.currentFile;
|
files += tab.currentFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!files.empty()) {
|
|
||||||
out_buf->appendf("[NodeEditor][Session]\n");
|
out_buf->appendf("[NodeEditor][Session]\n");
|
||||||
|
if (!files.empty()) {
|
||||||
out_buf->appendf("LastFiles=%s\n", files.c_str());
|
out_buf->appendf("LastFiles=%s\n", files.c_str());
|
||||||
out_buf->appendf("ActiveTab=%d\n", app->activeTab);
|
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);
|
ImGui::AddSettingsHandler(&h);
|
||||||
|
|||||||
Reference in New Issue
Block a user