basic graph and nodes + serialization
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraphVisualNode.h"
|
||||
|
||||
class WorldGraph final
|
||||
{
|
||||
public:
|
||||
WorldGraph() = default;
|
||||
WorldGraph(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes);
|
||||
|
||||
WorldGraph(const WorldGraph& other);
|
||||
WorldGraph(WorldGraph&& other) noexcept = default;
|
||||
|
||||
WorldGraph& operator=(const WorldGraph& other);
|
||||
WorldGraph& operator=(WorldGraph&& other) noexcept = default;
|
||||
|
||||
public:
|
||||
Variant Execute(Ref<WorldGraphVisualNodeBase> node, const WorldNodeParameters& params) const;
|
||||
WorldNodeBase* GetNode(Ref<WorldGraphVisualNodeBase> node) const;
|
||||
|
||||
private:
|
||||
void Compile(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes);
|
||||
std::unique_ptr<WorldNodeBase*[]> CopyMemory(HashMap<Ref<WorldGraphVisualNodeBase>, WorldNodeBase*>& nodeMap) const;
|
||||
|
||||
private:
|
||||
int MemorySize{};
|
||||
std::unique_ptr<WorldNodeBase*[]> CompiledData{};
|
||||
HashMap<Ref<WorldGraphVisualNodeBase>, WorldNodeBase*> NodeMap{};
|
||||
};
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include "modules/factory/include/Util/Span.h"
|
||||
|
||||
struct WorldGraphAllocatorBase
|
||||
{
|
||||
virtual ~WorldGraphAllocatorBase() = default;
|
||||
|
||||
virtual void* Allocate(tcb::span<const uint8_t> data) = 0;
|
||||
virtual void Clear() = 0;
|
||||
|
||||
template <typename T>
|
||||
T* Allocate(const T& val)
|
||||
{
|
||||
return static_cast<T*>(Allocate(tcb::span<const uint8_t>(reinterpret_cast<uint8_t const*>(&val), sizeof(T))));
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldGraphAllocator : public WorldGraphAllocatorBase
|
||||
{
|
||||
virtual ~WorldGraphAllocator() = default;
|
||||
WorldGraphAllocator() = default;
|
||||
WorldGraphAllocator(uint32_t totalSize) : Data {std::make_unique<uint8_t[]>(totalSize)}, Size{totalSize} {}
|
||||
|
||||
virtual void* Allocate(tcb::span<const uint8_t> data) override
|
||||
{
|
||||
auto size = (data.size_bytes() + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*); // make sure aligment is 8/4 bytes for pointers
|
||||
if (CurrentOffset + size > Size)
|
||||
throw std::exception{};
|
||||
|
||||
std::memcpy(Data.get() + CurrentOffset, data.data(), data.size_bytes());
|
||||
CurrentOffset += size;
|
||||
|
||||
return Data.get() + CurrentOffset - size;
|
||||
}
|
||||
|
||||
virtual void Clear() override
|
||||
{
|
||||
CurrentOffset = 0;
|
||||
}
|
||||
|
||||
void* GetCurrentAddress() const
|
||||
{
|
||||
return Data.get() + CurrentOffset;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> Data{};
|
||||
uint32_t Size{};
|
||||
uint32_t CurrentOffset{};
|
||||
};
|
||||
|
||||
struct WorldGraphSizeMeasurer : public WorldGraphAllocatorBase
|
||||
{
|
||||
virtual ~WorldGraphSizeMeasurer() = default;
|
||||
WorldGraphSizeMeasurer() = default;
|
||||
|
||||
virtual void* Allocate(tcb::span<const uint8_t> data) override
|
||||
{
|
||||
TotalSize += (data.size_bytes() + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void Clear() override
|
||||
{
|
||||
TotalSize = 0;
|
||||
}
|
||||
|
||||
uint32_t TotalSize{};
|
||||
};
|
||||
@@ -1,665 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <array>
|
||||
|
||||
#include "WorldGraphAllocator.h"
|
||||
#include "Util/FastNoiseLite.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "modules/factory/include/Core/Chunk.h"
|
||||
|
||||
// // last bit determines if it is a boolean or a float
|
||||
// // if it is a float, last bit of precision will be lost (not that bad)
|
||||
// // if it is a bool, the bool value will be stored on the second bit
|
||||
// // for floats the last bit is set
|
||||
// // for bools the last bit is unset
|
||||
// class BoolFloat
|
||||
// {
|
||||
// public:
|
||||
// BoolFloat() = default;
|
||||
// BoolFloat(bool val) {}
|
||||
// BoolFloat(float val) {}
|
||||
|
||||
// public:
|
||||
// void SetFloat(float val) { Data = reinterpret_cast<uint32_t&>(val) | 0b1u; }
|
||||
// void SetBool(bool val) { Data = (val << 1) & (~0b1u); }
|
||||
|
||||
// constexpr float IsFloat() const { return Data & 0b1u; }
|
||||
// constexpr float IsBool() const { return Data & (~0b1u); }
|
||||
|
||||
// float GetFloat() const { _ASSERT(IsFloat()); return reinterpret_cast<const float&>(Data); }
|
||||
// float GetBool() const { _ASSERT(IsBool()); return Data; }
|
||||
|
||||
// public:
|
||||
// operator float() const { return GetFloat(); }
|
||||
|
||||
// private:
|
||||
// uint32_t Data{};
|
||||
// };
|
||||
|
||||
struct WorldNodeParameters;
|
||||
|
||||
struct alignas(void*) WorldNodeBase
|
||||
{
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const = 0;
|
||||
virtual Variant::Type GetReturnType() const = 0;
|
||||
virtual Vector<Variant::Type> GetInputTypes() const = 0;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const = 0;
|
||||
virtual void SetInput(int index, WorldNodeBase* input) = 0;
|
||||
};
|
||||
|
||||
struct WorldNodeParameters
|
||||
{
|
||||
static constexpr int MaxQueryOffset = 4;
|
||||
static constexpr int PaddedChunkSide = Chunk::ChunkSize + MaxQueryOffset * 2;
|
||||
static constexpr int PaddedChunkSize = PaddedChunkSide * PaddedChunkSide;
|
||||
|
||||
typedef std::array<Tile, PaddedChunkSize> TileArray;
|
||||
|
||||
int X{ };
|
||||
int Y{ };
|
||||
int Seed{ };
|
||||
float FinalValueSubstract{ };
|
||||
|
||||
ChunkKey ChunkInfo{ };
|
||||
TileArray* GeneratedTiles{ };
|
||||
|
||||
Tile GetTile(int x, int y) const
|
||||
{
|
||||
auto bounds = GetGenerationBounds();
|
||||
if (unlikely(!bounds.has_point(Vector2i{x, y})))
|
||||
return {};
|
||||
// DEV_ASSERT(bounds.has_point(Vector2i{x, y}));
|
||||
|
||||
return (*GeneratedTiles)[(y - bounds.position.y) * PaddedChunkSide + (x - bounds.position.x)];
|
||||
}
|
||||
|
||||
static int GetArrayIndex(int x, int y)
|
||||
{
|
||||
return (y + MaxQueryOffset) * PaddedChunkSide + (x + MaxQueryOffset);
|
||||
}
|
||||
|
||||
Rect2i GetGenerationBounds() const
|
||||
{
|
||||
return ChunkInfo.GetBounds().grow(MaxQueryOffset);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TtoVariant
|
||||
{
|
||||
typedef Variant TVariant;
|
||||
};
|
||||
|
||||
template <typename Return, typename ... Inputs>
|
||||
struct WorldNodeTemplated : public WorldNodeBase
|
||||
{
|
||||
std::array<WorldNodeBase*, sizeof...(Inputs)> InputNodes{};
|
||||
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::get_type_t<Return>(); }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { Variant::get_type_t<Inputs>()... };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
bool valid{ true };
|
||||
for (auto input : InputNodes)
|
||||
{
|
||||
valid = valid && input;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override { Allocator->Allocate(*this); }
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
std::array<Variant, sizeof...(Inputs)> results{};
|
||||
for (int i{}; i < sizeof...(Inputs); ++i)
|
||||
{
|
||||
results[i] = InputNodes[i]->Evaluate(params);
|
||||
}
|
||||
|
||||
auto EvaluateFunctor = [this](typename TtoVariant<Inputs>::TVariant... args) -> Variant
|
||||
{
|
||||
return EvaluateT(args.get_unsafe_t<Inputs>()...);
|
||||
};
|
||||
|
||||
return std::apply(EvaluateFunctor, results);
|
||||
}
|
||||
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
InputNodes[index] = input;
|
||||
}
|
||||
|
||||
|
||||
virtual Return EvaluateT(Inputs...) const = 0;
|
||||
};
|
||||
|
||||
struct WorldNode_Add : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 + v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Subtract : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 - v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Multiply : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 * v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Divide : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 / v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Modulo : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return fmod(v0, v1);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Equal : public WorldNodeTemplated<bool, float, float>
|
||||
{
|
||||
virtual bool EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 == v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Smaller : public WorldNodeTemplated<bool, float, float>
|
||||
{
|
||||
virtual bool EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 < v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Greater : public WorldNodeTemplated<bool, float, float>
|
||||
{
|
||||
virtual bool EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 > v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_SmallerEqual : public WorldNodeTemplated<bool, float, float>
|
||||
{
|
||||
virtual bool EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 <= v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_GreaterEqual : public WorldNodeTemplated<bool, float, float>
|
||||
{
|
||||
virtual bool EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 >= v1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Negate : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return -v;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Abs : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::abs(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Ceil : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::ceil(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Floor : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::floor(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Sin : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::sin(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Cos : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::cos(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Tan : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::tan(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Exp : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::exp(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Pow : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return std::pow(v0, v1);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Max : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return std::max(v0, v1);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Min : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return std::min(v0, v1);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Clamp : public WorldNodeTemplated<float, float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1, float v2) const override
|
||||
{
|
||||
return std::clamp(v0, v1, v2);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Round : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::round(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Log : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return std::log(v);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Square : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v) const override
|
||||
{
|
||||
return v * v;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Lerp : public WorldNodeTemplated<float, float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float from, float to, float weight) const override
|
||||
{
|
||||
return Math::lerp(from, to, weight);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_OneMinus : public WorldNodeTemplated<float, float>
|
||||
{
|
||||
virtual float EvaluateT(float val) const override
|
||||
{
|
||||
return 1 - val;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_And : public WorldNodeTemplated<bool, bool, bool>
|
||||
{
|
||||
virtual bool EvaluateT(bool val0, bool val1) const override
|
||||
{
|
||||
return val0 && val1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Or : public WorldNodeTemplated<bool, bool, bool>
|
||||
{
|
||||
virtual bool EvaluateT(bool val0, bool val1) const override
|
||||
{
|
||||
return val0 || val1;
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Constant : public WorldNodeBase
|
||||
{
|
||||
float Value{};
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
return !std::_Is_nan(Value);
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
|
||||
{
|
||||
Allocator->Allocate(*this);
|
||||
}
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
DEV_ASSERT(false);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Branch : public WorldNodeBase
|
||||
{
|
||||
WorldNodeBase* InputBool{};
|
||||
WorldNodeBase* InputTrue{};
|
||||
WorldNodeBase* InputFalse{};
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
bool condition = InputBool->Evaluate(params).get_unsafe_bool();
|
||||
return condition ? InputTrue->Evaluate(params) : InputFalse->Evaluate(params);
|
||||
}
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::Type::FLOAT; }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { Variant::Type::BOOL, Variant::Type::FLOAT, Variant::Type::FLOAT };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
return InputBool && InputTrue && InputFalse;
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
|
||||
{
|
||||
Allocator->Allocate(*this);
|
||||
}
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: InputBool = input;
|
||||
case 1: InputTrue = input;
|
||||
case 2: InputFalse = input;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_NoiseBase : public WorldNodeBase
|
||||
{
|
||||
float Frequency{ 1.f };
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
float x = params.X * Frequency;
|
||||
float y = params.Y * Frequency;
|
||||
return EvaluateNoise(params.Seed, x, y);
|
||||
}
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
return Frequency != 0;
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
|
||||
{
|
||||
Allocator->Allocate(*this);
|
||||
}
|
||||
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
DEV_ASSERT(false);
|
||||
}
|
||||
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const = 0;
|
||||
};
|
||||
|
||||
struct WorldNode_Simplex : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
const float SQRT3 = (float)1.7320508075688772935274463415059;
|
||||
const float F2 = 0.5f * (SQRT3 - 1);
|
||||
float t = (x + y) * F2;
|
||||
x += t;
|
||||
y += t;
|
||||
|
||||
return fastnoiselitestatic::SingleSimplex<float>(seed, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_OpenSimplex : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
const float SQRT3 = (float)1.7320508075688772935274463415059;
|
||||
const float F2 = 0.5f * (SQRT3 - 1);
|
||||
float t = (x + y) * F2;
|
||||
x += t;
|
||||
y += t;
|
||||
|
||||
return fastnoiselitestatic::SingleOpenSimplex2S<float>(seed, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Perlin : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
return fastnoiselitestatic::SinglePerlin<float>(seed, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_ValueCubic : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
return fastnoiselitestatic::SingleValueCubic<float>(seed, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_Value : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
return fastnoiselitestatic::SingleValue<float>(seed, x, y);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_IsTile : public WorldNodeBase
|
||||
{
|
||||
int8_t RelativeX{};
|
||||
int8_t RelativeY{};
|
||||
TILE_TYPE TileType{};
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
return params.GetTile(params.X + RelativeX, params.Y + RelativeY).GetType() == TileType;
|
||||
}
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::BOOL; }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
|
||||
{
|
||||
Allocator->Allocate(*this);
|
||||
}
|
||||
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
DEV_ASSERT(false);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_TileDistance : public WorldNodeBase
|
||||
{
|
||||
int8_t Range{};
|
||||
TILE_TYPE TileType{};
|
||||
|
||||
private:
|
||||
struct SmallVectorI2{
|
||||
int8_t X; int8_t Y;
|
||||
SmallVectorI2(int8_t x, int8_t y) : X{ x }, Y{ y } {};
|
||||
SmallVectorI2() = default;
|
||||
};
|
||||
|
||||
static constexpr int ArraySize{(WorldNodeParameters::MaxQueryOffset * 2 + 1) * (WorldNodeParameters::MaxQueryOffset * 2 + 1) - 1};
|
||||
|
||||
static std::array<SmallVectorI2, ArraySize> CreateSortedOffsets()
|
||||
{
|
||||
std::array<SmallVectorI2, ArraySize> offsets{};
|
||||
int counter{};
|
||||
for (int y{-WorldNodeParameters::MaxQueryOffset}; y <= WorldNodeParameters::MaxQueryOffset; ++y)
|
||||
for (int x{-WorldNodeParameters::MaxQueryOffset}; x <= WorldNodeParameters::MaxQueryOffset; ++x)
|
||||
if (y != 0 && x != 0)
|
||||
{
|
||||
offsets[counter] = SmallVectorI2{ static_cast<int8_t>(x), static_cast<int8_t>(y) };
|
||||
++counter;
|
||||
}
|
||||
std::sort(offsets.begin(), offsets.end(), [] (SmallVectorI2 lhs, SmallVectorI2 rhs)
|
||||
{
|
||||
return rhs.X * rhs.X + rhs.Y * rhs.Y > lhs.X * lhs.X + lhs.Y * lhs.Y;
|
||||
});
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline static const std::array<SmallVectorI2, ArraySize> SortedOffsets{ CreateSortedOffsets() };
|
||||
|
||||
static std::array<int, WorldNodeParameters::MaxQueryOffset> CreateRangeOffsets()
|
||||
{
|
||||
std::array<int, WorldNodeParameters::MaxQueryOffset> offsets{};
|
||||
|
||||
for (int i{}; i < WorldNodeParameters::MaxQueryOffset; ++i)
|
||||
{
|
||||
offsets[i] = ArraySize - (((WorldNodeParameters::MaxQueryOffset - i) * 2 + 1) * ((WorldNodeParameters::MaxQueryOffset - i) * 2 + 1) - 1);
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline static const std::array<int, WorldNodeParameters::MaxQueryOffset> RangeOffsets{ CreateRangeOffsets() };
|
||||
|
||||
// inline static const SmallVectorI2 SortedOffset[] =
|
||||
// {
|
||||
// {+3, +3}, {-3, -3}, {+3, -3}, {-3, +3}, // 3
|
||||
// {+2, +3}, {+3, +2}, {-2, -3}, {-3, -2}, {-2, +3}, {-3, +2}, {+2, -3}, {+3, -2},
|
||||
// {+1, +3}, {+3, +1}, {-1, -3}, {-3, -1}, {-1, +3}, {-3, +1}, {+1, -3}, {+3, -1},
|
||||
// {+3, +0}, {-3, -0}, {+0, -3}, {-0, +3},
|
||||
// {+2, +2}, {-2, -2}, {+2, -2}, {-2, +2}, // 2
|
||||
// {+1, +2}, {+2, +1}, {-1, -2}, {-2, -1}, {-1, +2}, {-2, +1}, {+1, -2}, {+2, -1},
|
||||
// {+2, +0}, {-2, -0}, {+0, -2}, {-0, +2},
|
||||
// {+1, +1}, {-1, -1}, {+1, -1}, {-1, +1}, // 1
|
||||
// {+1, +0}, {-1, -0}, {+0, -1}, {-0, +1},
|
||||
// };
|
||||
|
||||
// inline static const int8_t RangeOffsets[] =
|
||||
// {
|
||||
// 0, 24, 40
|
||||
// };
|
||||
|
||||
public:
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
if (!params.ChunkInfo.GetBounds().has_point(Vector2i{params.X, params.Y})) return 16'384.f;
|
||||
|
||||
int maxRangeSQ = 16'384;
|
||||
for (int i{RangeOffsets[Range]}; i < 48; ++i)
|
||||
{
|
||||
auto offset = SortedOffsets[i];
|
||||
if (params.GetTile(params.X + offset.X, params.Y + offset.Y).GetType() == TileType)
|
||||
{
|
||||
maxRangeSQ = offset.X * offset.X + offset.Y * offset.Y;
|
||||
}
|
||||
}
|
||||
return sqrtf(static_cast<float>(maxRangeSQ));
|
||||
}
|
||||
virtual Variant::Type GetReturnType() const override { return Variant::FLOAT; }
|
||||
virtual Vector<Variant::Type> GetInputTypes() const override
|
||||
{
|
||||
return { };
|
||||
};
|
||||
virtual bool IsValid() const override
|
||||
{
|
||||
return Range >= 1 && Range <= 3;
|
||||
}
|
||||
virtual void Allocate(WorldGraphAllocatorBase* Allocator) const override
|
||||
{
|
||||
Allocator->Allocate(*this);
|
||||
}
|
||||
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
DEV_ASSERT(false);
|
||||
}
|
||||
};
|
||||
|
||||
// struct WorldNode_Cellular : public WorldNode_NoiseBase
|
||||
// {
|
||||
// virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
// {
|
||||
// const float SQRT3 = (float)1.7320508075688772935274463415059;
|
||||
// const float F2 = 0.5f * (SQRT3 - 1);
|
||||
// float t = (x + y) * F2;
|
||||
// x += t;
|
||||
// y += t;
|
||||
|
||||
// return fastnoiselite::FastNoiseLite::SingleCellular<float>(seed, x, y);
|
||||
// }
|
||||
// };
|
||||
@@ -1,174 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraphNode.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "modules/factory/include/Util/Helpers.h"
|
||||
|
||||
class WorldGraphVisualNodeBase : public Resource
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNodeBase, Resource);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual ~WorldGraphVisualNodeBase() = default;
|
||||
|
||||
public:
|
||||
Vector2i GetPosition() const { return Position; }
|
||||
TypedArray<WorldGraphVisualNodeBase> GetInputs() const
|
||||
{
|
||||
return VectorToTypedArray(InputNodes);
|
||||
}
|
||||
|
||||
void SetPosition(Vector2i pos) { Position = pos; }
|
||||
void SetInputNodes(TypedArray<WorldGraphVisualNodeBase> inputNodes)
|
||||
{
|
||||
InputNodes = TypedArrayToVector(inputNodes);
|
||||
RefreshInputs();
|
||||
}
|
||||
|
||||
bool NodeIsValid() const { return IsValid(); }
|
||||
TypedArray<int> NodeGetInputTypes() const { return VectorToTypedArrayCast<int>(GetInputTypes()); }
|
||||
int NodeGetOutputType() const { return GetOutputType(); }
|
||||
void NodeSetInput(int index, Ref<WorldGraphVisualNodeBase> input) { SetInput(index, input); }
|
||||
bool HasInternalNode() const { return InternalNode.get(); };
|
||||
|
||||
bool CanExecuteNode();
|
||||
|
||||
void SetInternalNode(std::unique_ptr<WorldNodeBase>&& node);
|
||||
WorldNodeBase* GetInternalNode() const { return InternalNode.get(); }
|
||||
void RefreshInputs();
|
||||
|
||||
public:
|
||||
virtual Vector<Variant::Type> GetInputTypes() const { return InternalNode ? InternalNode->GetInputTypes() : Vector<Variant::Type>{}; };
|
||||
virtual Variant::Type GetOutputType() const { return InternalNode ? InternalNode->GetReturnType() : Variant::Type{}; };
|
||||
virtual void SetInput(int index, Ref<WorldGraphVisualNodeBase> input)
|
||||
{
|
||||
InputNodes.set(index, input);
|
||||
InternalNode->SetInput(index, input.is_valid() ? input->InternalNode.get() : nullptr);
|
||||
}
|
||||
virtual bool IsValid() const
|
||||
{
|
||||
if (!InternalNode)
|
||||
print_error(String("No internal node for ") + get_class_name());
|
||||
if (!InternalNode->IsValid())
|
||||
print_error(String("node is invalid ") + get_class_name());
|
||||
return InternalNode && InternalNode->IsValid();
|
||||
}
|
||||
virtual void RefreshValues() {};
|
||||
|
||||
public:
|
||||
Vector2i Position{};
|
||||
Vector<Ref<WorldGraphVisualNodeBase>> InputNodes{};
|
||||
private:
|
||||
std::unique_ptr<WorldNodeBase> InternalNode{};
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_Math : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Math, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_Math();
|
||||
virtual ~WorldGraphVisualNode_Math() = default;
|
||||
|
||||
public:
|
||||
TypedArray<String> GetNodeNames() const;
|
||||
void SetNode(String nodeName);
|
||||
String GetNode() const { return NodeID; }
|
||||
|
||||
private:
|
||||
String NodeID{};
|
||||
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_Constant : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Constant, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_Constant();
|
||||
virtual ~WorldGraphVisualNode_Constant() = default;
|
||||
|
||||
private:
|
||||
void SetValue(float val);
|
||||
float GetValue() const;
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_If : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_If, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods() {};
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_If();
|
||||
virtual ~WorldGraphVisualNode_If() = default;
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_Noise : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Noise, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void SetNoiseType(String noiseType);
|
||||
String GetNoiseType() const { return NoiseType; }
|
||||
float GetFrequency() const { return Frequency; }
|
||||
|
||||
TypedArray<String> GetNoiseTypes() const;
|
||||
void SetFrequency(float val);
|
||||
|
||||
virtual void RefreshValues() override;
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_Noise();
|
||||
virtual ~WorldGraphVisualNode_Noise() = default;
|
||||
|
||||
private:
|
||||
String NoiseType{};
|
||||
float Frequency{};
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_Tile : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Tile, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_Tile();
|
||||
virtual ~WorldGraphVisualNode_Tile() = default;
|
||||
|
||||
public:
|
||||
void SetType(int type);
|
||||
void SetRelativeX(int offset);
|
||||
void SetRelativeY(int offset);
|
||||
|
||||
int GetType() const;
|
||||
int GetRelativeX() const;
|
||||
int GetRelativeY() const;
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_TileDistance : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_TileDistance, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_TileDistance();
|
||||
virtual ~WorldGraphVisualNode_TileDistance() = default;
|
||||
|
||||
public:
|
||||
void SetType(int type);
|
||||
void SetRange(int range);
|
||||
|
||||
int GetType() const;
|
||||
int GetRange() const;
|
||||
};
|
||||
109
include/WorldGraph/WorldGraph.h
Normal file
109
include/WorldGraph/WorldGraph.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraph/WorldGraphNode.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace WorldGraph {
|
||||
|
||||
class GraphSerializer;
|
||||
|
||||
/// Node-based graph for a single procedural world-generation pass.
|
||||
///
|
||||
/// Usage overview
|
||||
/// ──────────────
|
||||
/// 1. Call AddNode() for each node you need; keep the returned NodeIDs.
|
||||
/// 2. Wire outputs to inputs with Connect(fromNode, toNode, inputSlot).
|
||||
/// 3. Call Evaluate(outputNodeID, ctx) per cell to get its tile ID.
|
||||
///
|
||||
/// Evaluation is recursive and performed on the fly. Unconnected inputs
|
||||
/// receive a zero value of their declared type; the caller is responsible
|
||||
/// for checking IsValid() if all inputs must be wired.
|
||||
///
|
||||
/// Cycle detection runs at Connect() time: a connection that would create a
|
||||
/// cycle is rejected and false is returned.
|
||||
class Graph {
|
||||
public:
|
||||
using NodeID = uint32_t;
|
||||
static constexpr NodeID INVALID_ID = 0;
|
||||
|
||||
// ── Node management ────────────────────────────────────────────────────
|
||||
|
||||
/// Add a node; returns its assigned ID (always > INVALID_ID).
|
||||
NodeID AddNode(std::unique_ptr<Node> node);
|
||||
|
||||
/// Remove a node and sever every connection that references it.
|
||||
void RemoveNode(NodeID id);
|
||||
|
||||
/// Look up a node by ID (returns nullptr if not found).
|
||||
Node* GetNode(NodeID id);
|
||||
const Node* GetNode(NodeID id) const;
|
||||
|
||||
size_t NodeCount() const { return nodes.size(); }
|
||||
|
||||
// ── Connection management ─────────────────────────────────────────────
|
||||
|
||||
/// Wire the output of \p fromNode into input slot \p inputSlot of \p toNode.
|
||||
///
|
||||
/// Returns false (and makes no change) when:
|
||||
/// - either node is not in the graph, or
|
||||
/// - the connection would create a directed cycle.
|
||||
///
|
||||
/// Connecting to an already-wired slot replaces the previous connection.
|
||||
bool Connect(NodeID fromNode, NodeID toNode, int inputSlot);
|
||||
|
||||
/// Remove the wire going into \p toNode's \p inputSlot (no-op if not wired).
|
||||
void Disconnect(NodeID toNode, int inputSlot);
|
||||
|
||||
/// Return the source node wired to (toNode, inputSlot), or nullopt.
|
||||
std::optional<NodeID> GetInput(NodeID toNode, int inputSlot) const;
|
||||
|
||||
// ── Evaluation ────────────────────────────────────────────────────────
|
||||
|
||||
/// Recursively evaluate the subgraph rooted at \p outputNode for the cell
|
||||
/// described by \p ctx. Call AsInt() on the result to obtain a tile ID.
|
||||
///
|
||||
/// Unconnected inputs default to zero. Returns a zero Float value if
|
||||
/// \p outputNode is not in the graph.
|
||||
Value Evaluate(NodeID outputNode, const EvalContext& ctx) const;
|
||||
|
||||
/// True when every required input of \p outputNode (and all its transitive
|
||||
/// dependencies) is connected.
|
||||
bool IsValid(NodeID outputNode) const;
|
||||
|
||||
private:
|
||||
friend class GraphSerializer;
|
||||
|
||||
NodeID nextID { 1 };
|
||||
std::unordered_map<NodeID, std::unique_ptr<Node>> nodes;
|
||||
|
||||
// Connection map: (toNode, inputSlot) → fromNode
|
||||
struct ConnKey {
|
||||
NodeID toNode;
|
||||
int slot;
|
||||
bool operator==(const ConnKey& o) const noexcept {
|
||||
return toNode == o.toNode && slot == o.slot;
|
||||
}
|
||||
};
|
||||
struct ConnKeyHash {
|
||||
size_t operator()(const ConnKey& k) const noexcept {
|
||||
return std::hash<uint64_t>{}(
|
||||
(static_cast<uint64_t>(k.toNode) << 32) | static_cast<uint32_t>(k.slot));
|
||||
}
|
||||
};
|
||||
std::unordered_map<ConnKey, NodeID, ConnKeyHash> connections;
|
||||
|
||||
// Returns true if adding an edge (from → to) would create a cycle.
|
||||
// This holds when 'to' is already a transitive dependency of 'from'.
|
||||
bool WouldCreateCycle(NodeID from, NodeID to) const;
|
||||
|
||||
// Recursively collect 'id' and all its upstream dependencies into 'out'.
|
||||
void CollectDependencies(NodeID id, std::unordered_set<NodeID>& out) const;
|
||||
|
||||
Value EvaluateImpl(NodeID id, const EvalContext& ctx) const;
|
||||
};
|
||||
|
||||
} // namespace WorldGraph
|
||||
256
include/WorldGraph/WorldGraphNode.h
Normal file
256
include/WorldGraph/WorldGraphNode.h
Normal file
@@ -0,0 +1,256 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraph/WorldGraphTypes.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace WorldGraph {
|
||||
|
||||
// ─────────────────────────────── Node base ───────────────────────────────────
|
||||
|
||||
/// Abstract base for every node in the world-generation graph.
|
||||
///
|
||||
/// A node declares its output type and input types, and implements Evaluate()
|
||||
/// to compute an output Value from its inputs and the per-cell EvalContext.
|
||||
/// Type declarations are advisory — coercion is available on Value — but they
|
||||
/// allow the future visual editor to flag mismatched connections.
|
||||
class Node {
|
||||
public:
|
||||
virtual ~Node() = default;
|
||||
|
||||
virtual Type GetOutputType() const = 0;
|
||||
virtual std::vector<Type> GetInputTypes() const = 0;
|
||||
|
||||
/// Compute the output value.
|
||||
/// \p inputs is guaranteed to have exactly GetInputCount() entries.
|
||||
virtual Value Evaluate(const EvalContext& ctx,
|
||||
const std::vector<Value>& inputs) const = 0;
|
||||
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
size_t GetInputCount() const { return GetInputTypes().size(); }
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Math nodes ──────────────────────────────────
|
||||
|
||||
/// Outputs a + b (Float)
|
||||
class AddNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Add"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeFloat(in[0].AsFloat() + in[1].AsFloat());
|
||||
};
|
||||
};
|
||||
|
||||
/// Outputs a − b (Float)
|
||||
class SubtractNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Subtract"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeFloat(in[0].AsFloat() - in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs a × b (Float)
|
||||
class MultiplyNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Multiply"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeFloat(in[0].AsFloat() * in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs a / b (Float; returns 0 on division by zero)
|
||||
class DivideNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Divide"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
float b = in[1].AsFloat();
|
||||
if (b == 0.0f) return Value::MakeFloat(0.0f);
|
||||
return Value::MakeFloat(in[0].AsFloat() / b);
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Comparison nodes ────────────────────────────
|
||||
|
||||
/// Outputs true when a < b
|
||||
class LessNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Less"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsFloat() < in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs true when a > b
|
||||
class GreaterNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Greater"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsFloat() > in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs true when a <= b
|
||||
class LessEqualNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "LessEqual"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsFloat() <= in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs true when a >= b
|
||||
class GreaterEqualNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "GreaterEqual"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsFloat() >= in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs true when a == b (float comparison; use with care for non-integers)
|
||||
class EqualNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Float, Type::Float }; }
|
||||
std::string GetName() const override { return "Equal"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsFloat() == in[1].AsFloat());
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Boolean logic ───────────────────────────────
|
||||
|
||||
/// Outputs a && b
|
||||
class AndNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Bool, Type::Bool }; }
|
||||
std::string GetName() const override { return "And"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsBool() && in[1].AsBool());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs a || b
|
||||
class OrNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Bool, Type::Bool }; }
|
||||
std::string GetName() const override { return "Or"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 2);
|
||||
return Value::MakeBool(in[0].AsBool() || in[1].AsBool());
|
||||
}
|
||||
};
|
||||
|
||||
/// Outputs !a
|
||||
class NotNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Bool; }
|
||||
std::vector<Type> GetInputTypes() const override { return { Type::Bool }; }
|
||||
std::string GetName() const override { return "Not"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 1);
|
||||
return Value::MakeBool(!in[0].AsBool());
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Control flow ─────────────────────────────────
|
||||
|
||||
/// Selects between two inputs based on a boolean condition.
|
||||
///
|
||||
/// inputs[0] condition (Bool)
|
||||
/// inputs[1] value when true
|
||||
/// inputs[2] value when false
|
||||
///
|
||||
/// The output Value preserves the concrete type of whichever branch is chosen,
|
||||
/// so Int tile IDs pass through correctly even though GetOutputType() reports Float.
|
||||
class BranchNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override {
|
||||
return { Type::Bool, Type::Float, Type::Float };
|
||||
}
|
||||
std::string GetName() const override { return "Branch"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>& in) const override {
|
||||
DEV_ASSERT(in.size() == 3);
|
||||
return in[0].AsBool() ? in[1] : in[2];
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────── Source / constant nodes ─────────────────────
|
||||
|
||||
/// Outputs a fixed floating-point constant (no inputs).
|
||||
class ConstantNode : public Node {
|
||||
public:
|
||||
float value { 0.0f };
|
||||
explicit ConstantNode(float v = 0.0f) : value(v) {}
|
||||
|
||||
Type GetOutputType() const override { return Type::Float; }
|
||||
std::vector<Type> GetInputTypes() const override { return {}; }
|
||||
std::string GetName() const override { return "Constant"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>&) const override { return Value::MakeFloat(value); };
|
||||
};
|
||||
|
||||
/// Outputs a tile identifier as an integer (no inputs).
|
||||
class IDNode : public Node {
|
||||
public:
|
||||
int32_t tileID { 0 };
|
||||
explicit IDNode(int32_t id = 0) : tileID(id) {}
|
||||
|
||||
Type GetOutputType() const override { return Type::Int; }
|
||||
std::vector<Type> GetInputTypes() const override { return {}; }
|
||||
std::string GetName() const override { return "TileID"; }
|
||||
Value Evaluate(const EvalContext&, const std::vector<Value>&) const override { return Value::MakeInt(tileID); };
|
||||
};
|
||||
|
||||
/// Outputs the world-space X coordinate of the cell being evaluated.
|
||||
class PositionXNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Int; }
|
||||
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); };
|
||||
};
|
||||
|
||||
/// Outputs the world-space Y coordinate of the cell being evaluated.
|
||||
class PositionYNode : public Node {
|
||||
public:
|
||||
Type GetOutputType() const override { return Type::Int; }
|
||||
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); };
|
||||
};
|
||||
|
||||
} // namespace WorldGraph
|
||||
56
include/WorldGraph/WorldGraphSerializer.h
Normal file
56
include/WorldGraph/WorldGraphSerializer.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraph/WorldGraph.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace WorldGraph {
|
||||
|
||||
/// Serializes and deserializes a Graph to/from JSON using nlohmann/json.
|
||||
///
|
||||
/// JSON format
|
||||
/// ───────────
|
||||
/// {
|
||||
/// "nextId": <uint>,
|
||||
/// "nodes": [
|
||||
/// { "id": <uint>, "type": "<GetName()>", ...node-specific fields... },
|
||||
/// ...
|
||||
/// ],
|
||||
/// "connections": [
|
||||
/// { "from": <uint>, "to": <uint>, "slot": <int> },
|
||||
/// ...
|
||||
/// ]
|
||||
/// }
|
||||
///
|
||||
/// Node-specific fields
|
||||
/// ────────────────────
|
||||
/// Constant : "value" (float)
|
||||
/// TileID : "tileId" (int)
|
||||
/// All other nodes have no extra fields.
|
||||
class GraphSerializer {
|
||||
public:
|
||||
/// Serialise \p graph to a JSON object.
|
||||
static nlohmann::json ToJson (const Graph& graph);
|
||||
|
||||
/// Reconstruct a Graph from a JSON object produced by ToJson().
|
||||
/// Returns nullopt if the JSON is structurally invalid or contains an
|
||||
/// unrecognised node type.
|
||||
static std::optional<Graph> FromJson(const nlohmann::json& j);
|
||||
|
||||
/// Write the graph as pretty-printed JSON to \p path.
|
||||
/// Returns false if the file could not be opened/written.
|
||||
static bool Save(const Graph& graph, const std::string& path);
|
||||
|
||||
/// Read and reconstruct a Graph from \p path.
|
||||
/// Returns nullopt if the file cannot be read or the JSON is invalid.
|
||||
static std::optional<Graph> Load(const std::string& path);
|
||||
|
||||
private:
|
||||
/// Construct a Node of the given type name, reading any config fields from \p j.
|
||||
/// Returns nullptr for unrecognised type names.
|
||||
static std::unique_ptr<Node> CreateNode(const std::string& type,
|
||||
const nlohmann::json& j);
|
||||
};
|
||||
|
||||
} // namespace WorldGraph
|
||||
77
include/WorldGraph/WorldGraphTypes.h
Normal file
77
include/WorldGraph/WorldGraphTypes.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace WorldGraph {
|
||||
|
||||
/// Types that can flow through graph edges.
|
||||
enum class Type : uint8_t {
|
||||
Int, ///< Integer — used for tile IDs, positions, counts.
|
||||
Float, ///< Floating-point — used for math and noise.
|
||||
Bool, ///< Boolean — used for conditions / comparisons.
|
||||
};
|
||||
|
||||
/// A tagged value flowing along a graph edge.
|
||||
/// Provides coercion helpers so nodes can request any type they need.
|
||||
struct Value {
|
||||
Type type { Type::Float };
|
||||
union { int32_t i; float f; bool b; } data {};
|
||||
|
||||
inline static Value MakeInt (int32_t v) noexcept { Value r; r.type = Type::Int; r.data.i = v; return r; }
|
||||
inline static Value MakeFloat(float v) noexcept { Value r; r.type = Type::Float; r.data.f = v; return r; }
|
||||
inline static Value MakeBool (bool v) noexcept { Value r; r.type = Type::Bool; r.data.b = v; return r; }
|
||||
|
||||
float AsFloat() const noexcept;
|
||||
int32_t AsInt () const noexcept;
|
||||
bool AsBool () const noexcept;
|
||||
|
||||
bool operator==(const Value& o) const noexcept;
|
||||
bool operator!=(const Value& o) const noexcept { return !(*this == o); }
|
||||
};
|
||||
|
||||
/// Per-cell context forwarded to every node during graph evaluation.
|
||||
struct EvalContext {
|
||||
int32_t worldX { 0 }; ///< World-space tile column.
|
||||
int32_t worldY { 0 }; ///< World-space tile row.
|
||||
uint64_t seed { 0 }; ///< World seed for deterministic noise.
|
||||
// TODO: previous-pass tile accessor for tile-query nodes.
|
||||
};
|
||||
|
||||
inline float Value::AsFloat() const noexcept {
|
||||
switch (type) {
|
||||
case Type::Float: return data.f;
|
||||
case Type::Int: return static_cast<float>(data.i);
|
||||
case Type::Bool: return data.b ? 1.0f : 0.0f;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline int32_t Value::AsInt() const noexcept {
|
||||
switch (type) {
|
||||
case Type::Int: return data.i;
|
||||
case Type::Float: return static_cast<int32_t>(data.f);
|
||||
case Type::Bool: return data.b ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline bool Value::AsBool() const noexcept {
|
||||
switch (type) {
|
||||
case Type::Bool: return data.b;
|
||||
case Type::Int: return data.i != 0;
|
||||
case Type::Float: return data.f != 0.0f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool Value::operator==(const Value& o) const noexcept {
|
||||
if (type != o.type) return false;
|
||||
switch (type) {
|
||||
case Type::Int: return data.i == o.data.i;
|
||||
case Type::Float: return data.f == o.data.f;
|
||||
case Type::Bool: return data.b == o.data.b;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace WorldGraph
|
||||
Reference in New Issue
Block a user