multiple files can be open at the same time
This commit is contained in:
205
data/WorldGraphs/dirt.json
Normal file
205
data/WorldGraphs/dirt.json
Normal file
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"editor": {
|
||||
"nodePositions": {
|
||||
"1": [
|
||||
-1011.0,
|
||||
-214.0
|
||||
],
|
||||
"10": [
|
||||
-601.0,
|
||||
-250.0
|
||||
],
|
||||
"11": [
|
||||
-337.0,
|
||||
-265.0
|
||||
],
|
||||
"12": [
|
||||
-351.2725830078125,
|
||||
240.48638916015625
|
||||
],
|
||||
"2": [
|
||||
-740.0,
|
||||
-231.0
|
||||
],
|
||||
"3": [
|
||||
-885.0,
|
||||
-153.0
|
||||
],
|
||||
"4": [
|
||||
-472.0,
|
||||
-118.0
|
||||
],
|
||||
"5": [
|
||||
-144.0,
|
||||
-283.0
|
||||
],
|
||||
"6": [
|
||||
-341.0,
|
||||
-61.0
|
||||
],
|
||||
"7": [
|
||||
-23.0,
|
||||
-218.0
|
||||
],
|
||||
"9": [
|
||||
136.0,
|
||||
-300.0
|
||||
],
|
||||
"__worldOutput": [
|
||||
400.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"previewOriginX": 0,
|
||||
"previewOriginY": 0,
|
||||
"previewScale": 0.4000000059604645,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"graph": {
|
||||
"connections": [
|
||||
{
|
||||
"from": 1,
|
||||
"slot": 0,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 3,
|
||||
"slot": 1,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 11,
|
||||
"slot": 0,
|
||||
"to": 5
|
||||
},
|
||||
{
|
||||
"from": 6,
|
||||
"slot": 1,
|
||||
"to": 5
|
||||
},
|
||||
{
|
||||
"from": 5,
|
||||
"slot": 0,
|
||||
"to": 9
|
||||
},
|
||||
{
|
||||
"from": 7,
|
||||
"slot": 1,
|
||||
"to": 9
|
||||
},
|
||||
{
|
||||
"from": 2,
|
||||
"slot": 0,
|
||||
"to": 10
|
||||
},
|
||||
{
|
||||
"from": 10,
|
||||
"slot": 0,
|
||||
"to": 11
|
||||
},
|
||||
{
|
||||
"from": 4,
|
||||
"slot": 1,
|
||||
"to": 11
|
||||
}
|
||||
],
|
||||
"nextId": 13,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PositionY"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "Multiply"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "Constant",
|
||||
"value": 0.019999999552965164
|
||||
},
|
||||
{
|
||||
"frequency": 0.07880000025033951,
|
||||
"id": 4,
|
||||
"type": "SimplexNoise"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "Greater"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "Constant",
|
||||
"value": 0.1599999964237213
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"tileId": 1,
|
||||
"type": "TileID"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "IntBranch"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "Max"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Multiply"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"maxX": 1,
|
||||
"maxY": 1,
|
||||
"minX": -1,
|
||||
"minY": -1,
|
||||
"tileId": 1,
|
||||
"type": "QueryRange"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
120
data/WorldGraphs/stone.json
Normal file
120
data/WorldGraphs/stone.json
Normal file
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"editor": {
|
||||
"nodePositions": {
|
||||
"1": [
|
||||
-70.0,
|
||||
-259.0
|
||||
],
|
||||
"13": [
|
||||
-220.0,
|
||||
24.0
|
||||
],
|
||||
"15": [
|
||||
-77.0,
|
||||
8.0
|
||||
],
|
||||
"16": [
|
||||
-223.0,
|
||||
206.0
|
||||
],
|
||||
"2": [
|
||||
112.0,
|
||||
-289.0
|
||||
],
|
||||
"4": [
|
||||
-360.0,
|
||||
41.0
|
||||
],
|
||||
"__worldOutput": [
|
||||
400.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"previewOriginX": 0,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"graph": {
|
||||
"connections": [
|
||||
{
|
||||
"from": 15,
|
||||
"slot": 0,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 1,
|
||||
"slot": 1,
|
||||
"to": 2
|
||||
},
|
||||
{
|
||||
"from": 4,
|
||||
"slot": 0,
|
||||
"to": 13
|
||||
},
|
||||
{
|
||||
"from": 13,
|
||||
"slot": 0,
|
||||
"to": 15
|
||||
},
|
||||
{
|
||||
"from": 16,
|
||||
"slot": 1,
|
||||
"to": 15
|
||||
}
|
||||
],
|
||||
"nextId": 17,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"tileId": 1,
|
||||
"type": "TileID"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "IntBranch"
|
||||
},
|
||||
{
|
||||
"frequency": 0.10000000149011612,
|
||||
"id": 4,
|
||||
"type": "SimplexNoise"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "Abs"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "Less"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "Constant",
|
||||
"value": 0.3199999928474426
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
43
graph.json
43
graph.json
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
64
imgui.ini
64
imgui.ini
@@ -1,10 +1,10 @@
|
||||
[Window][Node Canvas]
|
||||
Pos=200,18
|
||||
Size=1080,702
|
||||
Pos=200,42
|
||||
Size=1720,1039
|
||||
|
||||
[Window][Settings]
|
||||
Pos=0,18
|
||||
Size=200,702
|
||||
Pos=0,42
|
||||
Size=200,1039
|
||||
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
@@ -34,3 +34,59 @@ Column 0 Sort=0v
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x3E490810,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x4546A666,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0xFE9B7E06,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0xD2055CEE,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0xCDBCCF58,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x9E317450,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x26C1F0A6,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x5DDF70E5,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x4AC5837A,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0xFB4E8B6C,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x8F6E2C61,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x42C29929,4]
|
||||
RefScale=13
|
||||
Column 0 Sort=0v
|
||||
|
||||
[Table][0x8FD0E34F,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
|
||||
|
||||
|
||||
@@ -366,19 +366,45 @@ public:
|
||||
/// Outputs the world-space X coordinate of the cell being evaluated.
|
||||
class PositionXNode : public Node {
|
||||
public:
|
||||
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 "PositionX"; }
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeInt(ctx.worldX); };
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeFloat(ctx.worldX); };
|
||||
};
|
||||
|
||||
/// Outputs the world-space Y coordinate of the cell being evaluated.
|
||||
class PositionYNode : public Node {
|
||||
public:
|
||||
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 "PositionY"; }
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeInt(ctx.worldY); };
|
||||
Value Evaluate(const EvalContext& ctx, const std::vector<Value>&) const override { return Value::MakeFloat(ctx.worldY); };
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Abs / Negate ────────────────────────────────
|
||||
|
||||
/// |a| (Float)
|
||||
class AbsNode : 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 "Abs"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
return Value::MakeFloat(std::abs(in[0].AsFloat()));
|
||||
}
|
||||
};
|
||||
|
||||
/// −a (Float)
|
||||
class NegateNode : 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 "Negate"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
return Value::MakeFloat(-in[0].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Min / Max / Clamp ───────────────────────────
|
||||
@@ -419,6 +445,37 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Map (range remap) ───────────────────────────
|
||||
|
||||
/// Remaps a value from [min0, max0] to [min1, max1].
|
||||
///
|
||||
/// inputs[0] value to remap (Float)
|
||||
///
|
||||
/// The four range endpoints are baked into the node at construction time.
|
||||
/// When min0 == max0 the output is min1 (avoids divide-by-zero).
|
||||
class MapNode : public Node {
|
||||
public:
|
||||
float min0 { 0.0f };
|
||||
float max0 { 1.0f };
|
||||
float min1 { 0.0f };
|
||||
float max1 { 1.0f };
|
||||
|
||||
MapNode() = default;
|
||||
MapNode(float mn0, float mx0, float mn1, float mx1)
|
||||
: min0(mn0), max0(mx0), min1(mn1), max1(mx1) {}
|
||||
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float }; }
|
||||
std::string GetName() const override { return "Map"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
float range0 = max0 - min0;
|
||||
if (range0 == 0.0f) return Value::MakeFloat(min1);
|
||||
float t = (in[0].AsFloat() - min0) / range0;
|
||||
return Value::MakeFloat(min1 + t * (max1 - min1));
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Int control flow ────────────────────────────
|
||||
|
||||
/// Selects between two integer inputs based on a boolean condition.
|
||||
|
||||
@@ -38,9 +38,18 @@ std::unique_ptr<Node> GraphSerializer::CreateNode(const std::string& type,
|
||||
if (type == "Pow") return std::make_unique<PowNode>();
|
||||
if (type == "Square") return std::make_unique<SquareNode>();
|
||||
if (type == "OneMinus") return std::make_unique<OneMinusNode>();
|
||||
if (type == "Abs") return std::make_unique<AbsNode>();
|
||||
if (type == "Negate") return std::make_unique<NegateNode>();
|
||||
if (type == "Min") return std::make_unique<MinNode>();
|
||||
if (type == "Max") return std::make_unique<MaxNode>();
|
||||
if (type == "Clamp") return std::make_unique<ClampNode>();
|
||||
if (type == "Map") {
|
||||
return std::make_unique<MapNode>(
|
||||
j.value("min0", 0.0f),
|
||||
j.value("max0", 1.0f),
|
||||
j.value("min1", 0.0f),
|
||||
j.value("max1", 1.0f));
|
||||
}
|
||||
|
||||
// Noise nodes (frequency baked in)
|
||||
if (type == "PerlinNoise") return std::make_unique<PerlinNoiseNode>(j.value("frequency", 0.01f));
|
||||
@@ -117,6 +126,11 @@ nlohmann::json GraphSerializer::ToJson(const Graph& g)
|
||||
} else if (const auto* qd = dynamic_cast<const QueryDistanceNode*>(node)) {
|
||||
jNode["tileId"] = qd->tileID;
|
||||
jNode["maxDistance"] = qd->maxDistance;
|
||||
} else if (const auto* mn = dynamic_cast<const MapNode*>(node)) {
|
||||
jNode["min0"] = mn->min0;
|
||||
jNode["max0"] = mn->max0;
|
||||
jNode["min1"] = mn->min1;
|
||||
jNode["max1"] = mn->max1;
|
||||
} else if (const auto* pn = dynamic_cast<const PerlinNoiseNode*>(node)) {
|
||||
jNode["frequency"] = pn->frequency;
|
||||
} else if (const auto* sn = dynamic_cast<const SimplexNoiseNode*>(node)) {
|
||||
|
||||
@@ -168,6 +168,38 @@ TEST_SUITE("WorldGraph::Nodes") {
|
||||
CHECK(n.Evaluate(c, {}).AsInt() == -3);
|
||||
}
|
||||
|
||||
// ── Abs / Negate ──────────────────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("AbsNode: positive input unchanged") {
|
||||
AbsNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(3.0f) }).AsFloat() == doctest::Approx(3.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("AbsNode: negative input becomes positive") {
|
||||
AbsNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(-7.5f) }).AsFloat() == doctest::Approx(7.5f));
|
||||
}
|
||||
|
||||
TEST_CASE("AbsNode: zero") {
|
||||
AbsNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.0f) }).AsFloat() == doctest::Approx(0.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("NegateNode: positive becomes negative") {
|
||||
NegateNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(4.0f) }).AsFloat() == doctest::Approx(-4.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("NegateNode: negative becomes positive") {
|
||||
NegateNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(-2.5f) }).AsFloat() == doctest::Approx(2.5f));
|
||||
}
|
||||
|
||||
TEST_CASE("NegateNode: zero") {
|
||||
NegateNode n;
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.0f) }).AsFloat() == doctest::Approx(0.0f));
|
||||
}
|
||||
|
||||
// ── Min / Max / Clamp ─────────────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("MinNode: returns smaller value") {
|
||||
@@ -216,6 +248,42 @@ TEST_SUITE("WorldGraph::Nodes") {
|
||||
== doctest::Approx(10.0f));
|
||||
}
|
||||
|
||||
// ── Map (range remap) ─────────────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("MapNode: maps midpoint correctly") {
|
||||
MapNode n(0.0f, 1.0f, 0.0f, 100.0f);
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.5f) }).AsFloat() == doctest::Approx(50.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("MapNode: maps min boundary") {
|
||||
MapNode n(0.0f, 1.0f, 10.0f, 20.0f);
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.0f) }).AsFloat() == doctest::Approx(10.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("MapNode: maps max boundary") {
|
||||
MapNode n(0.0f, 1.0f, 10.0f, 20.0f);
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(1.0f) }).AsFloat() == doctest::Approx(20.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("MapNode: maps noise range [-1, 1] to [0, 1]") {
|
||||
MapNode n(-1.0f, 1.0f, 0.0f, 1.0f);
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(-1.0f) }).AsFloat() == doctest::Approx(0.0f));
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat( 0.0f) }).AsFloat() == doctest::Approx(0.5f));
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat( 1.0f) }).AsFloat() == doctest::Approx(1.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("MapNode: inverted output range") {
|
||||
MapNode n(0.0f, 1.0f, 1.0f, 0.0f);
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.0f) }).AsFloat() == doctest::Approx(1.0f));
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(1.0f) }).AsFloat() == doctest::Approx(0.0f));
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(0.25f) }).AsFloat() == doctest::Approx(0.75f));
|
||||
}
|
||||
|
||||
TEST_CASE("MapNode: degenerate input range returns min1") {
|
||||
MapNode n(5.0f, 5.0f, 99.0f, 100.0f); // min0 == max0
|
||||
CHECK(n.Evaluate(ctx, { Value::MakeFloat(5.0f) }).AsFloat() == doctest::Approx(99.0f));
|
||||
}
|
||||
|
||||
// ── Int control flow ──────────────────────────────────────────────────────
|
||||
|
||||
TEST_CASE("IntBranchNode: selects true branch") {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user